About This Accessibility Rule
The headers attribute is one of the primary ways to programmatically associate data cells with their corresponding header cells in HTML tables. It works by referencing the id values of th elements. When these references point to elements outside the table — or to id values that don’t exist at all — the association breaks, and assistive technology can no longer convey the relationship between data and headers.
This primarily affects users who are blind or deafblind and rely on screen readers. When navigating a data table, screen readers announce the relevant column and row headers as users move from cell to cell. This “table navigation mode” is essential for understanding the data in context. If the headers attribute references are broken, users may hear no header announcements or incorrect ones, making it impossible to interpret the data.
This rule relates to WCAG 2.0, 2.1, and 2.2 Success Criterion 1.3.1: Info and Relationships (Level A), which requires that information, structure, and relationships conveyed visually are also available programmatically. It also applies to Section 508 requirements that row and column headers be identified for data tables and that markup be used to associate data cells with header cells in tables with two or more logical levels of headers.
How to Fix the Problem
There are three common approaches to associating headers with data cells:
Use the scope attribute (simplest approach)
For straightforward tables, adding scope="col" to column headers and scope="row" to row headers is the easiest and most reliable method. No id or headers attributes are needed.
Use id and headers attributes (for complex tables)
For tables with multiple levels of headers or irregular structures, you can assign an id to each th element and then list the relevant id values in each td‘s headers attribute. The critical rule is: every id referenced in a headers attribute must belong to a th in the same <table> element.
Use scope="colgroup" and scope="rowgroup"
For tables with headers that span multiple columns or rows, the colgroup and rowgroup values of scope can indicate which group of columns or rows a header applies to.
Examples
Incorrect: headers attribute references an id outside the table
In this example, the headers attribute on data cells references id="name", which exists on an element outside the table. This will trigger the rule violation.
<h2 id="name">Name</h2>
<table>
<tr>
<th id="time">Time</th>
</tr>
<tr>
<td headers="name time">Mary - 8:32</td>
</tr>
</table>
Incorrect: headers attribute references a non-existent id
Here, headers="runner" refers to an id that doesn’t exist anywhere in the table.
<table>
<tr>
<th id="name">Name</th>
<th id="time">1 Mile</th>
</tr>
<tr>
<td headers="runner">Mary</td>
<td headers="runner time">8:32</td>
</tr>
</table>
Correct: Using scope for a simple table
For simple tables, scope is the preferred method and avoids the pitfalls of headers entirely.
<table>
<caption>Greensprings Running Club Personal Bests</caption>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">1 Mile</th>
<th scope="col">5 km</th>
<th scope="col">10 km</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Mary</th>
<td>8:32</td>
<td>28:04</td>
<td>1:01:16</td>
</tr>
<tr>
<th scope="row">Betsy</th>
<td>7:43</td>
<td>26:47</td>
<td>55:38</td>
</tr>
</tbody>
</table>
Correct: Using id and headers for a complex table
When a table has multiple levels of headers, the headers attribute allows you to explicitly associate each data cell with the correct set of headers. All referenced id values are on th elements within the same table.
<table>
<caption>Items Sold August 2016</caption>
<thead>
<tr>
<td colspan="2"></td>
<th id="clothes" scope="colgroup" colspan="3">Clothes</th>
<th id="accessories" scope="colgroup" colspan="2">Accessories</th>
</tr>
<tr>
<td colspan="2"></td>
<th id="trousers" scope="col">Trousers</th>
<th id="skirts" scope="col">Skirts</th>
<th id="dresses" scope="col">Dresses</th>
<th id="bracelets" scope="col">Bracelets</th>
<th id="rings" scope="col">Rings</th>
</tr>
</thead>
<tbody>
<tr>
<th id="belgium" scope="rowgroup" rowspan="3">Belgium</th>
<th id="antwerp" scope="row">Antwerp</th>
<td headers="belgium antwerp clothes trousers">56</td>
<td headers="belgium antwerp clothes skirts">22</td>
<td headers="belgium antwerp clothes dresses">43</td>
<td headers="belgium antwerp accessories bracelets">72</td>
<td headers="belgium antwerp accessories rings">23</td>
</tr>
<tr>
<th id="gent" scope="row">Gent</th>
<td headers="belgium gent clothes trousers">46</td>
<td headers="belgium gent clothes skirts">18</td>
<td headers="belgium gent clothes dresses">50</td>
<td headers="belgium gent accessories bracelets">61</td>
<td headers="belgium gent accessories rings">15</td>
</tr>
<tr>
<th id="brussels" scope="row">Brussels</th>
<td headers="belgium brussels clothes trousers">51</td>
<td headers="belgium brussels clothes skirts">27</td>
<td headers="belgium brussels clothes dresses">38</td>
<td headers="belgium brussels accessories bracelets">69</td>
<td headers="belgium brussels accessories rings">28</td>
</tr>
</tbody>
</table>
In this complex example, each data cell’s headers attribute lists the id values of every relevant header — the country group, the city, the product category, and the specific product. Every referenced id belongs to a th within the same <table>, so the associations are valid and screen readers can announce the full context for each cell.
Tips for avoiding this issue
-
Double-check
idvalues. A common cause of this violation is a typo in either theidon thethor in theheadersattribute value on thetd. -
Don’t reuse
idvalues across tables. If you copy markup from one table to another, ensureidvalues are unique within the page and thatheadersattributes reference the correct table’sthelements. -
Prefer
scopewhen possible. For simple tables with a single row of column headers and/or a single column of row headers,scopeis simpler and less error-prone thanid/headers. -
Use
idandheadersfor genuinely complex tables — those with multiple header levels, merged cells, or irregular structures wherescopealone cannot convey all relationships.
Help us improve our guides
Detect accessibility issues automatically
Rocket Validator scans thousands of pages with Axe Core and the W3C Validator, finding accessibility issues across your entire site.