Table column or row header


In a data table, a column or row header is the cell that serves as a title for the other cells in that column or row. In HTML, it's the <th> tag that fulfills this role. Without it, a screen reader restores cells without context: the user hears values without knowing what they correspond to.


When you browse a table with your eyes, you move back and forth between cells and their headers without thinking about it. A screen reader does the same, provided the code tells it which cells are headers. Without this information, it reads raw numbers, without context. "€120." €120 of what?

#<th> and the scope attribute

The <th> tag marks a cell as a header. The scope attribute specifies its scope: col for a column, row for a row.

<table>
  <caption>Sales by region</caption>
  <thead>
    <tr>
      <th scope="col">Region</th>
      <th scope="col">Q1</th>
      <th scope="col">Q2</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">North</th>
      <td>€120</td>
      <td>€145</td>
    </tr>
    <tr>
      <th scope="row">South</th>
      <td>€98</td>
      <td>€110</td>
    </tr>
  </tbody>
</table>

With this markup, a screen reader announces "North, Q1: €120" when the user navigates to that cell. Without the <th> tags, it announces "€120". Nothing else.

For simple tables where the header is in the first row or first column, <th> alone is sufficient. Adding scope removes ambiguity when the table has headers in both dimensions, which is the W3C recommendation.

#The <td> disguised as a header trap

The most common mistake: putting header text in a <td> and styling it bold with CSS. Visually, the result is identical. To a screen reader, the cell remains an ordinary data cell. The association with other cells does not exist.

<!-- ❌ Looks like a header, but isn't one -->
<td><strong>Region</strong></td>
 
<!-- ✅ Header recognized by assistive technologies -->
<th scope="col">Region</th>

Another trap: placing the scope attribute on a <td>. The attribute is reserved for <th>. On a <td>, it has no effect.

#Complex tables: when scope isn't enough

When a table contains merged cells or sub-contexts, scope reaches its limits. You must then associate each data cell with its headers via the id and headers attributes, as Access42 details. The RGAA dedicates several criteria to this in its theme 5.

The best strategy remains to simplify: split a complex table into several simple tables. Users of screen readers will find their way better. Everyone else will too.

#In summary

A table header is a <th> tag, not a bold <td>. Add scope="col" or scope="row" whenever the table has headers in both directions. And if your table requires id/headers, ask yourself whether it wouldn't be clearer as two tables.

Share this article

Learn more