Skip to main content

About This Accessibility Rule

Screen readers rely on the programmatic relationships between header cells and data cells to help users navigate and understand tabular data. As a user moves through a table, the screen reader announces the relevant column or row header for each data cell, giving context to what would otherwise be just a raw value. When a <th> element doesn’t properly refer to any data cells, the screen reader either announces incorrect headers, skips the header entirely, or produces confusing output.

This primarily affects blind and deafblind users who depend on screen readers to interpret table structure. Without correct header associations, these users cannot understand what each piece of data represents or how it relates to other data in the table.

Related Standards

This rule maps 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 through presentation are programmatically determinable. A table header that doesn’t actually reference any data cells fails to convey the intended structural relationship.

It also relates to Section 508 requirements that row and column headers be identified for data tables, and that markup be used to associate data cells and header cells. The Trusted Tester guidelines similarly require that all data cells are programmatically associated with relevant headers.

How to Fix It

  1. Use the correct scope value on <th> elements. If the header applies to a column, use scope="col". If it applies to a row, use scope="row". A common mistake is using scope="row" on column headers or vice versa, which means the header doesn’t actually point to any data cells in the intended direction.

  2. Ensure every <th> has data cells to reference. A <th> that sits in a position where it has no associated <td> elements (e.g., an empty row or column) should either be restructured or converted to a <td>.

  3. For simple tables, use scope on <th> elements. This is the simplest and most reliable approach when there is a single row of column headers and/or a single column of row headers.

  4. For complex tables with multiple levels of headers, use id and headers attributes. Assign a unique id to each <th>, then reference the appropriate id values in the headers attribute of each <td>. Note that <th> elements themselves should not use the headers attribute to reference other <th> elements — keep headers on <td> elements.

  5. If using headers attributes, ensure the referenced id values point to elements with text content that is available to screen readers.

Examples

Incorrect: scope="row" Used on Column Headers

In this example, the <th> elements are column headers, but they are incorrectly scoped to row. This means the headers don’t reference the data cells below them, so screen readers cannot associate “Last Name,” “First Name,” or “City” with their respective columns.

<table>
  <caption>Teddy bear collectors</caption>
  <tr>
    <th scope="row">Last Name</th>
    <th scope="row">First Name</th>
    <th scope="row">City</th>
  </tr>
  <tr>
    <td>Phoenix</td>
    <td>Imari</td>
    <td>Henry</td>
  </tr>
  <tr>
    <td>Zeki</td>
    <td>Rome</td>
    <td>Min</td>
  </tr>
</table>

Correct: scope="col" Used on Column Headers

The fix is straightforward — change scope="row" to scope="col" so the headers correctly reference the data cells beneath them.

<table>
  <caption>Teddy bear collectors</caption>
  <tr>
    <th scope="col">Last Name</th>
    <th scope="col">First Name</th>
    <th scope="col">City</th>
  </tr>
  <tr>
    <td>Phoenix</td>
    <td>Imari</td>
    <td>Henry</td>
  </tr>
  <tr>
    <td>Zeki</td>
    <td>Rome</td>
    <td>Min</td>
  </tr>
</table>

Correct: Using Both Column and Row Headers

When a table has headers in both the first row and the first column, use scope="col" for the column headers and scope="row" for the row headers.

<table>
  <caption>Quarterly sales (in thousands)</caption>
  <tr>
    <td></td>
    <th scope="col">Q1</th>
    <th scope="col">Q2</th>
    <th scope="col">Q3</th>
  </tr>
  <tr>
    <th scope="row">Widgets</th>
    <td>50</td>
    <td>80</td>
    <td>120</td>
  </tr>
  <tr>
    <th scope="row">Gadgets</th>
    <td>30</td>
    <td>45</td>
    <td>60</td>
  </tr>
</table>

Correct: Complex Table Using id and headers

For tables with multiple levels of headers, use the id and headers approach to create explicit associations.

<table>
  <caption>Student exam results</caption>
  <tr>
    <td></td>
    <th id="math" scope="col">Math</th>
    <th id="science" scope="col">Science</th>
  </tr>
  <tr>
    <th id="maria" scope="row">Maria</th>
    <td headers="maria math">95</td>
    <td headers="maria science">88</td>
  </tr>
  <tr>
    <th id="james" scope="row">James</th>
    <td headers="james math">72</td>
    <td headers="james science">91</td>
  </tr>
</table>

In this example, each <td> explicitly references both its row header and column header, so a screen reader can announce something like “Maria, Math, 95” as the user navigates the table.

Help us improve our guides

Was this guide helpful?

Detect accessibility issues automatically

Rocket Validator scans thousands of pages with Axe Core and the W3C Validator, finding accessibility issues across your entire site.

Ready to validate your sites?
Start your free trial today.