Skip to main content

Axe Core Guide

Axe Core 4.11

Table cells that use the headers attribute must only refer to cells in the same table

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 id values. A common cause of this violation is a typo in either the id on the th or in the headers attribute value on the td.
  • Don’t reuse id values across tables. If you copy markup from one table to another, ensure id values are unique within the page and that headers attributes reference the correct table’s th elements.
  • Prefer scope when possible. For simple tables with a single row of column headers and/or a single column of row headers, scope is simpler and less error-prone than id/headers.
  • Use id and headers for genuinely complex tables — those with multiple header levels, merged cells, or irregular structures where scope alone cannot convey all relationships.

Learn more:

Last reviewed: February 14, 2026

Was this guide helpful?

Related Accessibility Rules