Skip to content

Data cells need headers

  • Rule: axe/td-has-header
  • Checks: Non-empty <td> elements in larger <table> must have an associated table header
  • WCAG: 1.3.1
  • Fix class: mechanical: A fix needs no authored text or visual decision.
  • People who use a screen reader or a braille display, most of them blind.

Header cells are marked up as <td> and styled to look like headers

Section titled “Header cells are marked up as <td> and styled to look like headers”

Change them to <th>, with scope="col" on column headers and scope="row" on row headers.

<table>
<caption>Quarterly sales</caption>
<tr>
<td></td>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
</tr>
<tr>
<th scope="row">North</th>
<td>120</td>
<td>135</td>
</tr>
<tr>
<th scope="row">South</th>
<td>98</td>
<td>110</td>
</tr>
</table>

The table has no header row or column at all

Section titled “The table has no header row or column at all”

Add header cells that name what each column, and where useful each row, holds. Decide the header text from what the data means, not from guesses.

The table only lays out content and holds no data relationships

Section titled “The table only lays out content and holds no data relationships”

Lay the content out with CSS instead. If the table must stay, add role="presentation" and make sure its content reads in a sensible order.

axe skips labeled cells, so the rule passes, but labels set up no header relationships: each replaces its cell’s content as the name people hear, and must be kept in step with the data.

axe-core’s documentation of the rule

CORA’s fix guidance adapts content from accessibility-agents, under the MIT License: see its notice.