All non-empty <td> elements in tables larger than 3 by 3 must have an associated table header
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.
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.
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.
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”.
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: Old Versions of VoiceOver did Not Support the id
+ headers
Method
Up until Mac OSX 10.10.2, VoiceOver did not support the ability to read table headers with the id + headers method. Some versions even read the wrong headers with the data cells. Fortunately, the current version of VoiceOver does read the data and header associations correctly.
The Algorithm (in simple terms)
Checks that data tables are marked up semantically and have the correct header structure.