About This HTML Issue
Every ID in the headers attribute of a th has to be the id of another th in the same table, and the validator flags each one that isn't.
On a header cell, headers ties a sub-header to the header cells above it or beside it. When a "Sales" header spans the "Q1" and "Q2" columns, <th headers="sales">Q1</th> says that Q1 belongs to Sales. The value is a space-separated list of IDs, and the validator checks each one on its own, so a single cell can trigger this message more than once.
The referenced header is usually where things went wrong. The validator reports the ID when:
- the group header has no
id, or a different one (IDs are case-sensitive, soSalesdoesn't matchsales) - the
idsits on atdstyled to look like a heading, or on thetr, thetheador aspaninside theth, instead of on thethitself - the
thwith thatidbelongs to another table, like a nested table or a separate table used as a fixed header row
Screen readers rely on these references to tell users which headers a cell falls under. When an ID doesn't resolve to a th, the sub-header loses its link to the group. Put the id on the group header's th, changing a td into a th if needed. If that header no longer exists, remove its ID from headers.
The standard also forbids a header cell that targets itself, either directly or through a chain of other headers' headers attributes. The validator doesn't report that loop, so check for it yourself when header cells point at each other.
Invalid example
<table>
<tr>
<td id="sales" colspan="2">Sales</td>
</tr>
<tr>
<th headers="sales">Q1</th>
<th headers="sales">Q2</th>
</tr>
<tr>
<td>1200</td>
<td>1350</td>
</tr>
</table>
Valid example
<table>
<tr>
<th id="sales" colspan="2">Sales</th>
</tr>
<tr>
<th headers="sales">Q1</th>
<th headers="sales">Q2</th>
</tr>
<tr>
<td>1200</td>
<td>1350</td>
</tr>
</table>
Find issues like this automatically
Rocket Validator scans thousands of pages in seconds, detecting HTML issues across your entire site.