Data table has missing or incomplete header cell markup

Link to Data table has missing or incomplete header cell markup copied to clipboard

semantic-data-table-headers

Rule ID:
semantic-data-table-headers
User Impact:
Critical
WCAG :
1.3.1.b

Rule

Data tables must use semantic markup to associate data cells with their corresponding header cell(s).

Background

Tables are static structures that contain one or more rows of data cells. Each data cell is associated with one or more corresponding header cells, which label, or provide context for, the data.

example
First name Last name
Jane Doe
John Doe

The visual arrangement of a table is intended to help users quickly understand the relationship between each data cell and its corresponding headers. Users who rely on assistive technology, however, may not be able to perceive these relationships. Tables must use appropriate semantic markup so assistive technologies can provide users with context for the data they encounter as they navigate throughout the table.

Good Examples

The examples in this section demonstrate how you can properly associate data cells with their corresponding headers in a data table.

note

All examples represent data from WebAIM's Screen Reader User Survey #9.

Native HTML table elements and attributes

Native HTML <table> element and attributes are widely supported by assistive technology and should be used whenever possible, especially when the table's data/header relationships are simple.

example
Most common screen reader and browser combinations
Screen Reader and Browser Number of Respondents Percent of Respondents
JAWS with Chrome 500 32.5%
NVDA with Chrome 246 16.0%
JAWS with Edge 194 12.6%
NVDA with Firefox 149 9.7%
<table>
  <thead>
    <tr>
      <th scope="col">Screen Reader and Browser</th>
      <th scope="col">Number of Respondents</th>
      <th scope="col">Percent of Respondents</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">JAWS with Chrome</th>
      <td>500</td>
      <td>32.5%</td>
    </tr>
    <tr>
      <th scope="row">NVDA with Chrome</th>
      <td>246</td>
      <td>16.0%</td>
    </tr>
    <tr>
      <th scope="row">JAWS with Edge</th>
      <td>194</td>
      <td>12.6%</td>
    </tr>
    <tr>
      <th scope="row">NVDA with Firefox</th>
      <td>149</td>
      <td>9.7%</td>
    </tr>
  </tbody>
</table>

ARIA table

If the table is comprised of <div> and <span> elements, you can use the "cell" role to identify data cells, and the "columnheader" and "rowheader" roles to identify headers.

warning

In order for this header-association method to work properly, other table-related roles must also be used on the correct elements.

example
<div role="table">
  <div role="rowgroup">
    <div role="row">
      <span role="columnheader">Screen Reader and Browser</th>
      <span role="columnheader">Number of Respondents</th>
      <span role="columnheader">Percent of Respondents</th>
    </div>
  </div>
  <div>
    <div role="row">
      <span role="rowheader">JAWS with Chrome</th>
      <span role="cell">500</td>
      <span role="cell">32.5%</td>
    </div>
    <div role="row">
      <span role="rowheader">NVDA with Chrome</th>
      <span role="cell">246</td>
      <span role="cell">16.0%</td>
    </div>
    <div role="row">
      <span role="rowheader">JAWS with Edge</th>
      <span role="cell">194</td>
      <span role="cell">12.6%</td>
    </div>
    <div role="row">
      <span role="rowheader">NVDA with Firefox</th>
      <span role="cell">149</td>
      <span role="cell">9.7%</td>
    </div>
  </div>
</div>

Using headers/id association

This technique is recommended for complex data tables where column headers change or repeat throughout the table, or where at least some of the data cells are described by three or more headers cells.

First, give each header cell an id attribute whose value is unique within the entire document. Then, add a headers attribute to each data cell whose value is a space-separated list of the cell's headers.

tip

In the following code example, the "headers" value for the header cells themselves point to a single empty cell, whose id is "blank". This is not strictly necessary, but can help prevent some assistive technologies from announcing an inappropriate header value when navigating to a header cell.

Technique source: Tables with Multi-Level Headers | Web Accessibility Initiative (WAI) | W3C.

example
Most common screen reader and browser combinations
Number of Respondents Percent of Respondents
Chrome
Jaws 500 32.5%
NVDA 246 16.0%
Firefox
NVDA 149 9.7%
Jaws 74 4.8%
<table>
  <caption>Most common screen reader and browser combinations</caption>
  <thead>
    <tr>
      <th id="blank"></th>
      <th id="number" headers="blank">Number of Respondents</th>
      <th id="percent" headers="blank">Percent of Respondents</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th id="chrome" headers="blank" colspan="3">Chrome</th>
    </tr>
    <tr>
      <th id="jaws-chrome" headers="blank">Jaws</th>
      <td headers="number chrome jaws-chrome">500</td>
      <td headers="percent chrome jaws-chrome">32.5%</td>
    </tr>
    <tr>
      <th id="nvda-chrome" headers="blank">NVDA</th>
      <td headers="number chrome nvda-chrome">246</td>
      <td headers="percent chrome nvda-chrome">16.0%</td>
    </tr>
    <tr>
      <th id="firefox" headers="blank" colspan="3">Firefox</th>
    </tr>
    <tr>
      <th id="nvda-firefox" headers="blank">NVDA</th>
      <td headers="number firefox nvda-firefox">149</td>
      <td headers="percent firefox nvda-firefox">9.7%</td>
    </tr>
    <tr>
      <th id="jaws-firefox" headers="blank">Jaws</th>
      <td headers="number firefox jaws-firefox">74</td>
      <td headers="percent firefox jaws-firefox">4.8%</td>
    </tr>
  </tbody>
</table>

Additional Resources