Accessible Data Tables

Data Tables

Tables have a bad rep in HTML programming. For years they were misused for page layouts. Tables are meant for displaying tabular data. Let's look at how you can build some complex data tables that are also accessible.

While tables are easy to understand visually, we need to give screen reader users a bit more structure to give the data cells more context. A poorly written table will appear to the screen reader user as a series of data points and no reference to what they mean. We'll use the scope and headers attributes to add these contextual hooks.

Some of the code in this page was borrowed from the NVDA user guide.

Scope attribute connects data and title

The <th> is used in the top row of a data table or as the first cell of a row. Add the scope="row|col" attribute to define if the table header cell references data vertically or horizontally.

Sample code for a table proper table headers

<table>
<thead>
<tr>
<th scope="col"> Dept. Code/ Class Number</th>
<th scope="col"> Section</th>
...</tr>
</thead>
<tbody>
<tr>
<th scope="row"> BIO 100</th>
<td>1</td>
...</tr>
<tr>
</tbody>
Table with scope attribute
Dept. Code/ Class Number Section Current Enrollment Rm # Days Start Time End Time
BIO 100 1 13 5 Mon,Wed,Fri 10:00 11:00
BIO 100 2 7 5 Tue,Thu 11:00 12:30
BIO 205 1 9 6 Tue,Thu 09:00 10:30
BIO 315 1 3 6 Mon,Wed,Fri 13:00 14:00
BUS 150 1 15 13 Mon,Wed,Fri 09:00 10:00
BUS 210 1 9 13 Mon,Wed,Fri 08:00 09:00
Listen: You should hear the screen reader announce the header text before each data cell. VoiceOver announces a table that uses the scope attribute

Inaccessible table with no scope attribute

The following table is the same table as above, but headers and cells are not correctly associated.

Sample code for a table missing scope attribute

<table>
<tbody>
<tr>
<td><strong> Dept. Code/ Class Number</strong></td>
<td><strong> Section</strong></td>
...</tr>
<tr>
<td><strong> BIO 100</strong></td>
<td>1</td>
...</tr>
Sample table
Dept. Code/ Class Number Section Current Enrollment Rm # Days Start Time End Time
BIO 100 1 13 5 Mon,Wed,Fri 10:00 11:00
BIO 100 2 7 5 Tue,Thu 11:00 12:30
BIO 205 1 9 6 Tue,Thu 09:00 10:30
BIO 315 1 3 6 Mon,Wed,Fri 13:00 14:00
BUS 150 1 15 13 Mon,Wed,Fri 09:00 10:00
BUS 210 1 9 13 Mon,Wed,Fri 08:00 09:00

Listen: You will notice the cells are read independently. It's very difficult to keep track of what column each data element refers to. VoiceOver announces an inaccessible table

Tables with multiple row headers

The following complex table contains two columns of row headers. Headers and cells are correctly associated using the id="foo" and headers="foo" attributes. The caption is a semantic header for the table. The summary attribute let's user's know if the table is interesting enough to browse.

Sample code for a complex table with headers attribute

<table summary="This table has the ages and birthdates for Shelly's Daughters Jackie, Beth, and Jenny"><caption> Shelly's Daughters
</caption>
<thead>
<tr>
<td></td>
<th id="name">Name</th>
<th id="age">Age</th>
<th id="birthday">Birthday</th>
</tr>
</thead>
<tbody>
<tr>
<th id="birth" rowspan="2">by birth</th>
<th id="jackie">Jackie</th>
<td headers="age,birth,jackie">5</td>
<td headers="birthday,birth,jackie">April 5</td>
</tr>
Sample table
Shelly's Daughters
Name Age Birthday
by birth Jackie 5 April 5
Beth 8 January 14
by marriage Jenny 12 Feb 12
Listen: Notice the data cell for Jackie's Age. It will read out the three headers (by birth, Jackie, Age) before the number 5. You will also notice the table's summary and caption are mentioned before going into the data. VoiceOver announces a table with headers attribute

Inaccessible table with missing scope and headers attributes

The following table is the same table as above, but headers and cells are not correctly associated.

Sample code for a complex table without headers and scope attributes

<strong>Shelly's Daughters </strong>
<table>
<tbody>
<tr>
<td></td>
<td><strong>Name</strong></td>
<td><strong>Age</strong></td>
<td><strong>Birthday</strong></td>
</tr>
<tr>
<td rowspan="2"><strong>by birth</strong></td>
<td><strong>Jackie</strong></td>
<td>5</td>
<td>April 5</td>
</tr>
Sample table
Shelly's Daughters
Name Age Birthday
by birth Jackie 5 April 5
Beth 8 January 14
by marriage Jenny 12 Feb 12

Listen: Notice the data cell for Jackie's Age. It will be read as an individual element, with no context of the headers. VoiceOver announces a table that does not use the headers or scope attributes

What you can do

  1. Make sure your data tables use <th scope="col|row">
  2. Use the headers="foo" attribute on complicated data tables
  3. Use the summary="foo" attribute when the table can be summarized easily. A good example would be "The scores by inning for the final World Series game. San Francisco Giants beat the Texas Rangers 3 to 1 in 9 innings."
  4. Remember: God kills a kitten everytime you use a table for page layout.