Associating Headers with Data Cells
For very simple tables, most screen readers will do a good job of automatically determining the association of data cells with their headers. However, there are many cases in which it is difficult for software to infer this association without a human-level understanding of the content itself. Therefor, it is considered a best practice to always explicitly define it. There are two methods for doing so: scoping the <th> elements and referring to the headers in the <td> elements via the headers attribute (we call this the headers and id technique).
Scoping Headers
In the <th> element, the scope attribute can take one of four reserved values:
row: labels the associated row.col: labels the associated column.rowgroup: labels the associated spanned rows.colgroup: labels the associated spanned columns.
You can see each of these used in the following example:
TRIAD | USAGE | DOUBLING | |||
|---|---|---|---|---|---|
| Root Pos. | First Inv. | Second Inv. | |||
Major Keys | ii | yes | yes | no | root |
| iii | yes | yes | no | root | |
| vi | yes | yes | no | root | |
| vii° | no | yes | no | third | |
Minor Keys | ii | no | rare | no | third |
| III | yes | yes | no | root | |
| VI | yes | yes | no | root | |
| VII | yes | yes | no | root | |
| ii° | no | yes | no | third | |
| vi° | no | yes | no | third | |
| vii° | no | yes | no | third | |
| III+ | no | yes | no | third | |
<table>
<thead>
<tr>
<td rowspan="2"> </td>
<th rowspan="2" scope="col">
TRIAD
</th>
<th colspan="3" scope="colgroup">
USAGE
</th>
<th rowspan="2" scope="col">
DOUBLING
</th>
</tr>
<tr>
<th>Root Pos.</th>
<th>First Inv.</th>
<th>Second Inv.</th>
</tr>
</thead>
<tbody>
<tr>
<th rowspan="4" scope="rowgroup">
Major Keys
</th>
<th scope="row">ii</th>
<td>yes</td>
<td>yes</td>
<td>no</td>
<td>root</td>
</tr>
<tr>
<th scope="row">iii</th>
<td>yes</td>
<td>yes</td>
<td>no</td>
<td>root</td>
</tr>
<tr>
<th scope="row">vi</th>
<td>yes</td>
<td>yes</td>
<td>no</td>
<td>root</td>
</tr>
<tr>
<th scope="row">vii°</th>
<td>no</td>
<td>yes</td>
<td>no</td>
<td>third</td>
</tr>
<tr>
<th rowspan="8" scope="rowgroup">
Minor Keys
</th>
<th scope="row">ii</th>
<td>no</td>
<td>rare</td>
<td>no</td>
<td>third</td>
</tr>
<tr>
<th scope="row">III</th>
<td>yes</td>
<td>yes</td>
<td>no</td>
<td>root</td>
</tr>
<tr>
<th scope="row">VI</th>
<td>yes</td>
<td>yes</td>
<td>no</td>
<td>root</td>
</tr>
<tr>
<th scope="row">VII</th>
<td>yes</td>
<td>yes</td>
<td>no</td>
<td>root</td>
</tr>
<tr>
<th scope="row">ii°</th>
<td>no</td>
<td>yes</td>
<td>no</td>
<td>third</td>
</tr>
<tr>
<th scope="row">vi°</th>
<td>no</td>
<td>yes</td>
<td>no</td>
<td>third</td>
</tr>
<tr>
<th scope="row">vii°</th>
<td>no</td>
<td>yes</td>
<td>no</td>
<td>third</td>
</tr>
<tr>
<th scope="row">III+</th>
<td>no</td>
<td>yes</td>
<td>no</td>
<td>third</td>
</tr>
</tbody>
</table>Headers and ID Technique
For more complex tables, it is necessary to explicitly define the header of each data cell itself. This is accomplished by using the headers attribute on each <td> element to refer to the id of its header(s) in a space-separated list. The order of the ids determines the order they are read to the screen reader user. For example:
| Example 1 Ltd | Example 2 Co | |
|---|---|---|
| Contact | James Phillips | Marie Beauchamp |
| Position | Sales Director | Sales Manager |
| jp@1ltd.example.com | marie@2co.example.com | |
| Example 3 Ltd | Example 4 Inc | |
| Contact | Suzette Jones | Alex Howe |
| Position | Sales Officer | Sales Director |
| Suz@ltd3.example.com | howe@4inc.example.com |
<table>
<tbody>
<tr>
<td> </td>
<th id="co1">Example 1 Ltd</th>
<th id="co2">Example 2 Co</th>
</tr>
<tr>
<th id="c1">Contact</th>
<td headers="co1 c1">James Phillips</td>
<td headers="co2 c1">Marie Beauchamp</td>
</tr>
<tr>
<th id="p1">Position</th>
<td headers="co1 p1">Sales Director</td>
<td headers="co2 p1">Sales Manager</td>
</tr>
<tr>
<th id="e1">Email</th>
<td headers="co1 e1">jp@1ltd.example.com</td>
<td headers="co2 e1">marie@2co.example.com</td>
</tr>
<tr>
<td> </td>
<th id="co3">Example 3 Ltd</th>
<th id="co4">Example 4 Inc</th>
</tr>
<tr>
<th id="c2">Contact</th>
<td headers="co3 c2">Suzette Jones</td>
<td headers="co4 c2">Alex Howe</td>
</tr>
<tr>
<th id="p2">Position</th>
<td headers="co3 p2">Sales Officer</td>
<td headers="co4 p2">Sales Director</td>
</tr>
<tr>
<th id="e2">Email</th>
<td headers="co3 e2">Suz@ltd3.example.com</td>
<td headers="co4 e2">howe@4inc.example.com</td>
</tr>
</tbody>
</table>Which Method Do I Choose?
Use header scoping wherever possible and headers and id only when necessary. The latter is more work and introduces more chances for human error.
If the X-axis headers are all in a single row or multiple contiguous rows at the top of the table and if the Y-axis headers are all in a single column or multiple contiguous columns at the left of the table, use scoping. Otherwise, use headers and id.
The preceding Scoping Headers and Headers and ID Technique table examples demonstrate this rule.
Never mix techniques. If you use scope on your <th> cells, <td> cells should never have headers attributes, and vice versa: if you use headers on your <td> cells, <th> cells should never have scope attributes.