Tables and Accessibility
There are two types of tables: layout tables and data tables. Layout tables are used to visually organize content on a website and data tables are used to present data so column information is related to row information. Keep reading to find out more about accessibility techniques for all types of tables.
Layout Table Accessibility
Layout tables are used to organize the design and content of websites. Most websites use tables for layout purposes. Here are the main points to keep in mind
Content organization
text-based browsers and assistive technology read tables from left to right and top to bottom.
Avoid using data table markup
You should never use data table tags or attributes in layout tables because it's bad HTML code and confuses screen readers.
These are the tags and attributes you should avoid in layout tables
- Caption tag <caption>
- Table Headers <th>
- Scope attribute
And for Optimal Accessibility
For best accessibility results websites would avoid using tables for layout purposes and instead use Cascading Style Sheets to control all design elements of a website. This option can be technically challenging for many web designers and it is not required for BCC websites.
Data Table Accessibility
Data tables organize data into a chart or spreadsheet in rows and columns. A data table is different from a layout table in that the column information presented is related to the row information.
When it comes to making data tables accessible, there are two types of data tables: simple and complex. A simple data table has up to one row of column headers and up to one column of row headers. A complex data table has two or more logical levels of row or column headers. Simple data tables are much simpler to make accessible. Because of this it is usually a good idea to simplify your tables when possible.
Example of a simple data table:
| Name | Phone Number | Age | Weight |
|---|---|---|---|
| Steve Nelson | 425/555-2186 | 54 | 130 lbs. |
| Maria Sanchez | 425/555-8741 | 43 | 120 lbs. |
( Notice how the above table only has table headers on the top row.)
Example of a complex data table:
| Meals | Hotels | Transport | subtotals | |
|---|---|---|---|---|
| San Jose | ||||
| 25-Aug-97 | 37.74 | 112.00 | 45.00 | |
| 26-Aug-97 | 27.28 | 112.00 | 45.00 | |
| subtotals | 65.02 | 224.00 | 90.00 | 379.02 |
| Seattle | ||||
| 27-Aug-97 | 96.25 | 109.00 | 36.00 | |
| 28-Aug-97 | 35.00 | 109.00 | 36.00 | |
| subtotals | 131.25 | 218.00 | 72.00 | 421.25 |
| Totals | 196.27 | 442.00 | 162.00 | 800.27 |
(Notice how the above table has multiple levels of column headers.)
Simple Data Tables
To make a simple data table accessible you need to make sure that table headers use at table header tag (<th>) on all table headers. Most WYSIWYG (what you see is what you get) HTML applications should allow you to easly make an html table cell a table header cell.
Example:
| Name | Phone Number | Age | Weight |
|---|---|---|---|
| Steve Nelson | 425/555-2186 | 54 | 130 lbs. |
| Maria Sanchez | 425/555-8741 | 43 | 120 lbs. |
View HTML code:
<table width="80%" border="1" cellspacing="1" cellpadding="2"> <tr> <th>Name</th> <th>Phone Number</th> <th>Age</th> <th>Weight</th> </tr> <tr> <td>Steve Nelson</td> <td>425/555-2186</td> <td>54</td> <td>130 lbs.</td> </tr> <tr> <td>Maria Sanchez</td> <td>425/555-8741</td> <td>43</td> <td>120 lbs.</td> </tr> </table>
Notice how the table cells for the top row use the <th> tag instead of the <td> tag. This tells browsers that the contents of those table cells are table headers. In the above example it is acceptable to make the entire left column of table cells as table headers.
Complex Data Tables
Complex data tables are a little more cumbersome to make accessible and in most cases you will need to modify the HTML code to make these tables accessible. Like simple data tables, you still need to mark all table headers using the <th> tag. Since complex data tables have multiple levels of logical row or column headers, you need to use other HTML tags and attributes to associate table data with the different levels of table headers. There are multiple methods you can use to make complex data tables accessible, but this will depend on the data table or your personal preference.
HTML allows various ways to associate table content.
- You can group table rows with the <thead>, <tbody> and <tfoot> tags. read more from W3C
- You can group table columns using the col or colgroup tags. read more from W3C.
- You can use the scope attribute to associate a row, column, row group or a column group to a table header cell.
- You can also use the axis, headers and id attributes together to create complex relationships between table cells and respective table header cells. Read more from W3C.
Using the scope attribute
One of the easiest ways of making your table accessible is through the use of the scope atribute. The scope attribute can associate rows, columns, row groups and column groups to a table cell.
The scope attribute can have four different values: "row", "col", "rowgroup" or "colgroup." By applying the value of 'row' to the scope attribute in a table header you associate all table cells in that row to the particular table header. By applying the value of 'col' to the scope attribute in a table header you associate all table cells in that column to the particular table header. On the table below you will notice how the scope attribute associate table cells to 2 different levels of column headers.
Example table using scope="col":
| Winter | Summer | |||
|---|---|---|---|---|
| Morning | Afternoon | Morning | Afternoon | |
| Wilma | 9-11 | 12-6 | 7-11 | 12-3 |
| Fred | 10-11 | 12-6 | 9-11 | 12-5 |
View HTML code
<table> <tr> <th> </th> <th colspan="2" scope="col" >Winter</th> <th colspan="2" scope="col" >Summer</th> </tr> <tr> <th> </th> <th scope="col" >Morning</th> <th scope="col" >Afternoon</th> <th scope="col" >Morning</th> <th scope="col" >Afternoon</th> </tr> <tr> <td scope="row" >Wilma</td> <td>9-11</td> <td>12-6</td> <td>7-11</td> <td>12-3</td> </tr> <tr> <td scope="row" >Fred</td> <td>10-11</td> <td>12-6</td> <td>9-11</td> <td>12-5</td> </tr> </table>
Scope vs. Headers and ID attributes
It is much easier to establish relationships between table cells and headers using the scope attribute.
Example of table using Scope attribute:
| Name | Cups | Type of Coffee | Sugar? |
|---|---|---|---|
| T. Sexton | 10 | Espresso | No |
| J. Dinnen | 5 | Decaf | Yes |
View HTML code
<table border="1" summary="Summary: This table charts the number of cups of coffee consumed by each senator, the type of coffee (decaf or regular), and whether taken with sugar."> <tr> <th scope="col">Name</th> <th scope="col">Cups</th> <th scope="col" abbr="type">Type of Coffee</th> <th scope="col">Sugar?</th> <tr> <td>T. Sexton</td> <td>10</td> <td>Espresso</td> <td>No</td> <tr> <td>J. Dinnen</td> <td>5</td> <td>Decaf</td> <td>Yes</td> </table>
Example of same table using headers and id attributes:
| Name | Cups | Type of Coffee | Sugar? |
|---|---|---|---|
| T. Sexton | 10 | Espresso | No |
| J. Dinnen | 5 | Decaf | Yes |
View HTML code
<table border="1" summary="Summary: This table charts the number of cups of coffee consumed by each senator, the type of coffee (decaf or regular), and whether taken with sugar."> <tr> <th id="t1">Name</th> <th id="t2">Cups</th> <th id="t3" abbr="Type">Type of Coffee</th> <th id="t4">Sugar?</th> </tr> <tr> <td headers="t1">T. Sexton</td> <td headers="t2">10</td> <td headers="t3">Espresso</td> <td headers="t4">No</td> </tr> <tr> <td headers="t1">J. Dinnen</td> <td headers="t2">5</td> <td headers="t3">Decaf</td> <td headers="t4">Yes</td> </tr> </table>
A speech synthesizer might render thes table as follows:
Summary: This table charts the number of cups of coffee consumed by each senator, the type of coffee (decaf or regular), and whether
taken with sugar.
Name: T. Sexton, Cups: 10, Type: Espresso, Sugar: No
Name: J. Dinnen, Cups: 5, Type: Decaf, Sugar: Yes
Note: how the header "Type of Coffee" is abbreviated to "Type" using the abbr attribute.
Using Axis for far more complicated tables
To create complex relationships between table cells, you will need to use the axis attribute along with the headers and id attributes
Example:
| Meals | Hotels | Transport | subtotals | |
|---|---|---|---|---|
| San Jose | ||||
| 25-Aug-97 | 37.74 | 112.00 | 45.00 | |
| 26-Aug-97 | 27.28 | 112.00 | 45.00 | |
| subtotals | 65.02 | 224.00 | 90.00 | 379.02 |
| Seattle | ||||
| 27-Aug-97 | 96.25 | 109.00 | 36.00 | |
| 28-Aug-97 | 35.00 | 109.00 | 36.00 | |
| subtotals | 131.25 | 218.00 | 72.00 | 421.25 |
| Totals | 196.27 | 442.00 | 162.00 | 800.27 |
View HTML code
<table border="1">
<caption>
Travel Expense Report
</caption>
<tr>
<th>
<th id="header2" axis="expenses">Meals
<th id="header3" axis="expenses">Hotels
<th id="header4" axis="expenses">Transport
<td>subtotals</td>
<tr>
<th id="header6" axis="location">San Jose
<th>
<th>
<th>
<td>
<tr>
<td id="header7" axis="date">25-Aug-97
<td headers="header6 header7 header2">37.74
<td headers="header6 header7 header3">112.00
<td headers="header6 header7 header4">45.00
<td>
<tr>
<td id="header8" axis="date">26-Aug-97
<td headers="header6 header8 header2">27.28
<td headers="header6 header8 header3">112.00
<td headers="header6 header8 header4">45.00
<td>
<tr>
<td>subtotals
<td>65.02</td>
<td>224.00
<td>90.00
<td>379.02
<tr>
<th id="header10" axis="location">Seattle
<th>
<th>
<th>
<td>
<tr>
<td id="header11" axis="date">27-Aug-97
<td headers="header10 header11 header2">96.25
<td headers="header10 header11 header3">109.00
<td headers="header10 header11 header4">36.00
<td>
<tr>
<td id="header12" axis="date">28-Aug-97
<td headers="header10 header12 header2">35.00
<td headers="header10 header12 header3">109.00
<td headers="header10 header12 header4">36.00
<td>
<tr>
<td>subtotals
<td>131.25
<td>218.00
<td>72.00
<td>421.25
<tr>
<th>Totals
<td>196.27
<td>442.00
<td>162.00
<td>800.27
</table>
A speech synthesizer might render it by speaking the following:
San Jose
25-Aug-97: Meals: 37.74, Hotels: 112.00, Transport: 45.00
26-Aug-97: Meals: 27.28, Hotels: 112.00, Transport: 45.00
subtotals: Meals: 65.02, Hotels: 224.00, Transport: 90.00, subtotals: 379.02
etc.
Alternatively, the user may be interested only in a particular column, and with the appropriate markup can instruct the speech synthesizer to read it as follows:
Meals
San Jose
25-Aug-97: 37.74
26-Aug-97: 27.28
subtotals: 65.02Seattle
27-Aug-97: 96.25
28-Aug-97: 35.00
subtotals: 131.25Totals: 196.24



