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

> Canonical HTML version: https://rocketvalidator.com/accessibility-validation/axe/4.11/td-headers-attr
> Attribution: Rocket Validator (https://rocketvalidator.com)
> License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

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.

```html
<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.

```html
<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.

```html
<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.

```html
<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.
