Non-empty <td> elements in larger <table> must have an associated table header

Rule ID: td-has-header
Ruleset: axe-core 4.9
User Impact: Critical
Guidelines: WCAG 2.1 Experimental, WCAG 2.0 Experimental, Section 508, Trusted Tester, EN 301 549

Accessibility testing for dev teams - No experience required

Find and fix up to 80% of accessibility issues with axe DevTools Pro. Get started with your free trial today. No credit card needed.

Compliance Data & Impact

User Impact

Critical
Minor
Critical

Disabilities Affected

  • Blind
  • Deafblind

Standard(s)

  • WCAG 2.1 Experimental
  • WCAG 2.0 Experimental
  • Section 508
  • Trusted Tester
  • EN 301 549

WCAG Success Criteria [WCAG 2.1 Experimental]

  • 1.3.1: MUST: Info and Relationships

WCAG Success Criteria [WCAG 2.0 Experimental]

  • 1.3.1: MUST: Info and Relationships

Section 508 Guidelines

  • 1194.22: MUST: Web based intranet and Internet Information & Applications
  • 1194.22 (g): MUST: Row and column headers shall be identified for data tables.” and “(h) Markup shall be used to associate data cells and header cells for data tables that have two or more logical levels of row or column headers.

Trusted Tester Guidelines

  • 14.B: MUST: All data cells are programmatically associated with relevant headers.

    How to Fix the Problem

    To fix the problem, ensure that each non-empty data cell in a large table has one or more table headers. All table data cells (td) must have a table header to ensure screen reader users can make sense of tabular data.

    Note: A table is considered large if it is 3 or more cells wide and 3 or more cells high.

    Example: Simple Data Table with <th scope="col"> and <th scope="row">

    To markup a table cell as a header cell, change the <td> to a <th>. You will see that doing this to our example table causes the top row to have bolded, centered text.

    Greensprings Running Club Personal Bests
    Name 1 mile 5 km 10 km
    Mary 8:32 28:04 1:01:16
    Betsy 7:43 26:47 55:38
    Matt 7:55 27:29 57:04
    Todd 7:01 24:21 50:35

    HTML Code

    <table class="data">
    <caption>
    Greensprings Running Club Personal Bests
    </caption>
      <thead>
        <tr>
         <th scope="col">Name</th>
          <th scope="col">1 mile</th>
          <th scope="col">5 km</th>
          <th scope="col">10 km</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th scope="row">Mary</th>
          <td>8:32</td>
          <td>28:04</td>
          <td>1:01:16</td>
        </tr>
        <tr>
          <th scope="row">Betsy</th>
          <td>7:43</td>
          <td>26:47</td>
          <td>55:38</td>
        </tr>
        <tr>
          <th scope="row">Matt</th>
          <td>7:55</td>
          <td>27:29</td>
          <td>57:04</td>
        </tr>
        <tr>
          <th scope="row">Todd</th>
          <td>7:01</td>
          <td>24:21</td>
          <td>50:35</td>
        </tr>
      </tbody>
    </table>

    Note: the visual aspects of table borders, fonts, margins, backgrounds, etc. can be defined using CSS.

    Example: Complex table with id + headers

    Complex tables benefit from the id + headers method of associating header cell with data cells. This method is time consuming, as every cell must be marked up with an identification of the row and column of each cell.

    Where possible, an easier option may be to plan your data presented in such as way that you can break up a complex table into a series of simpler tables. These tables may also be more useful for the general audience.

    In the example below, scope attributes have been replaced with id attributes on the headers. All of the data cells contain a headers attribute. The headers attribute can take a list of id values, each separated by a space, for each of the relevant headers. For instance, the second cell in the second row has a headers value of "mary 1m" indicating that this cell is related to two headers: the row header cell for "mary" and the column header cell for "1m".

    Example 2 (column group headers):
      Females Males
    Mary Betsy Matt Todd
    1 mile 8:32 7:43 7:55 7:01
    5 km 28:04 26:47 27:29 24:21
    10 km 1:01:16 55:38 57:04 50:35

    HTML Code

    <table class="data complex" border="1">
    <caption>
    Example 2 (column group headers): 
    </caption>
    <tr>
    <td rowspan="2"><span class="offscreen">empty</span></td>
    <th colspan="2" id="females2">Females</th>
    <th colspan="2" id="males2">Males</th>
    </tr>
    <tr>
    <th width="40" id="mary2">Mary</th>
    <th width="35" id="betsy2">Betsy</th>
    <th width="42" id="matt2">Matt</th>
    <th width="42" id="todd2">Todd</th>
    </tr>
    <tr>
    <th width="39" id="mile1_2">1 mile</th>
    <td headers="females2 mary2 mile1_2">8:32</td>
    <td headers="females2 betsy2 mile1_2">7:43</td>
    <td headers="males2 matt2 mile1_2">7:55</td>
    <td headers="males2 todd2 mile1_2">7:01</td>
    </tr>
    <tr>
    <th id="km5_2">5 km</th>
    <td headers="females2 mary2 km5_2">28:04</td>
    <td headers="females2 betsy2 km5_2">26:47</td>
    <td headers="males2 matt2 km5_2">27:29</td>
    <td headers="males2 todd2 km5_2">24:21</td>
    </tr>
    <tr>
    <th id="km10_2">10 km</th>
    <td headers="females2 mary2 km10_2">1:01:16</td>
    <td headers="females2 betsy2 km10_2">55:38</td>
    <td headers="males2 matt2 km10_2">57:04</td>
    <td headers="males2 todd2 km10_2">50:35</td>
    </tr>gt;

    This method creates an explicit association between the data cells and header cells. Though tedious to mark up by hand, this approach is relatively easy to program with a server-side scripting language (PHP, .net, JSP, Python, etcetera) for tables of data from a database.

    Note: Check target screen reader and browser for issues with id + headers Method

    Some screen reader and browser combinations have issues with the ability to read table headers with the id + headers method. Some versions even read the wrong headers with the data cells. Be sure to check your target screen reader and browser combinations for compatibility.

    Why it Matters

    Screen readers have a specific way of announcing tables. When tables are not properly marked up, this creates the opportunity for confusing or inaccurate screen reader output.

    When tables are not marked up semantically and do not have the correct header structure, screen reader users cannot correctly perceive the relationships between the cells and their contents visually.

    Rule Description

    Data table markup can be tedious and confusing. Tables must be marked up done semantically and with the correct header structure. Screen readers have features to ease table navigation, but tables must be marked up accurately for these features to work correctly.

    The Algorithm (in simple terms)

    Checks that data tables are marked up semantically and have the correct header structure.

    Resources

    Refer to the complete list of axe 4.9 rules.

    Was this information helpful?

    You have already given your feedback, thank you..

    Your response was as follows:

    Was this information helpful?
    Date/Time feedback was submitted: