# Data cells in tables larger than 3x3 should have associated headers.

> Canonical HTML version: https://rocketvalidator.com/accessibility-validation/accesslint/0.16/adaptable/td-has-header
> Attribution: Rocket Validator (https://rocketvalidator.com)
> License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

Data cells in a table that is larger than 3×3 need explicit header associations so that screen reader users can understand what each cell means. When a sighted user scans a table, the row and column headers are visually apparent. Screen readers, however, read cells one at a time and must rely on programmatic relationships between data cells and their headers to announce context like "Revenue, Q3, $4.2M." Without these associations, a screen reader may announce a bare value such as "$4.2M" with no indication of what it refers to.

This rule applies to tables with more than three columns or more than three rows. In very small tables (3×3 or smaller), the surrounding context is usually enough for screen readers to convey meaning. As tables grow, the distance between a header and a data cell increases, and the need for explicit associations becomes more pressing.

## Why this matters

This rule maps to WCAG 2 success criterion **1.3.1 Info and Relationships (Level A)**. That criterion requires that information, structure, and relationships conveyed through visual presentation are also available programmatically. When table headers are not associated with their data cells, structure that is obvious on screen is invisible to assistive technology.

The primary users affected are people who use screen readers, including users who are blind or have low vision. Without header associations, these users must memorize column and row positions to make sense of data, which is error prone and often impractical for larger tables.

## How to fix it

There are two main techniques.

### Use `th` elements with the `scope` attribute

For straightforward tables where headers appear in the first row, the first column, or both, wrap each header in a `<th>` element and add a `scope` attribute. Set `scope="col"` for column headers and `scope="row"` for row headers. This tells the screen reader which data cells each header applies to.

### Use the `headers` attribute on `td` elements

For tables with irregular structures, such as merged cells or headers that span multiple levels, the `scope` attribute may not be enough. In these cases, give each `<th>` a unique `id` and then reference those IDs in the `headers` attribute of each `<td>`. Separate multiple IDs with spaces.

## Examples

### Incorrect: table with no header associations

In this table, screen readers have no way to associate data cells with their column or row labels.

```html
<table>
  <tr>
    <td></td>
    <td>Q1</td>
    <td>Q2</td>
    <td>Q3</td>
    <td>Q4</td>
  </tr>
  <tr>
    <td>Revenue</td>
    <td>$1.1M</td>
    <td>$1.3M</td>
    <td>$4.2M</td>
    <td>$3.8M</td>
  </tr>
  <tr>
    <td>Expenses</td>
    <td>$0.9M</td>
    <td>$1.0M</td>
    <td>$2.1M</td>
    <td>$1.7M</td>
  </tr>
  <tr>
    <td>Profit</td>
    <td>$0.2M</td>
    <td>$0.3M</td>
    <td>$2.1M</td>
    <td>$2.1M</td>
  </tr>
</table>
```

### Correct: using `th` and `scope`

Each header cell is a `<th>` with the appropriate `scope` value. Screen readers can now announce "Revenue, Q3, $4.2M."

```html
<table>
  <tr>
    <td></td>
    <th scope="col">Q1</th>
    <th scope="col">Q2</th>
    <th scope="col">Q3</th>
    <th scope="col">Q4</th>
  </tr>
  <tr>
    <th scope="row">Revenue</th>
    <td>$1.1M</td>
    <td>$1.3M</td>
    <td>$4.2M</td>
    <td>$3.8M</td>
  </tr>
  <tr>
    <th scope="row">Expenses</th>
    <td>$0.9M</td>
    <td>$1.0M</td>
    <td>$2.1M</td>
    <td>$1.7M</td>
  </tr>
  <tr>
    <th scope="row">Profit</th>
    <td>$0.2M</td>
    <td>$0.3M</td>
    <td>$2.1M</td>
    <td>$2.1M</td>
  </tr>
</table>
```

### Correct: using the `headers` attribute for a complex table

When a table has multi-level headers or spans, the `headers` attribute gives full control over which headers apply to each cell.

```html
<table>
  <tr>
    <td></td>
    <th id="h1" scope="col">Q1</th>
    <th id="h2" scope="col">Q2</th>
    <th id="h3" scope="col">Q3</th>
    <th id="h4" scope="col">Q4</th>
  </tr>
  <tr>
    <th id="revenue" scope="row">Revenue</th>
    <td headers="revenue h1">$1.1M</td>
    <td headers="revenue h2">$1.3M</td>
    <td headers="revenue h3">$4.2M</td>
    <td headers="revenue h4">$3.8M</td>
  </tr>
  <tr>
    <th id="expenses" scope="row">Expenses</th>
    <td headers="expenses h1">$0.9M</td>
    <td headers="expenses h2">$1.0M</td>
    <td headers="expenses h3">$2.1M</td>
    <td headers="expenses h4">$1.7M</td>
  </tr>
</table>
```

The `scope` approach is simpler and should be the default choice. Reserve the `headers` attribute for tables where `scope` cannot express the relationships accurately, such as tables with cells that span multiple rows or columns, or tables with two or more levels of headers.
