Tables
Tabular data SHOULD be represented in a <table>
.
First and foremost, tables should never be used for layout purposes. Only use tables for tabular data (information with meaning dependent on it being in columns or rows in a grid). When proper HTML markup is used to create a table, readers can navigate through a table one cell at a time and hear the column and row headers spoken to them.
Don't create fake tables.
Because tables display poorly on mobile devices, developers have often created "fake tables." Using many <div>
elements to create columns and rows gave the visual appearance of a table without using <table>
markup. Don't do this. This method breaks the ability of screen readers to communicate the semantic meaning and structure to blind users.
The caption
element
Using the caption
element as a child of the table
element provides information about what that table contains. It must be the first thing in the table
tag, as screen readers read the caption or name of the table when users navigate to the table, giving users a sense of what the table is about.
-
Data tables SHOULD have a programmatically-associated caption or name.
-
The name/caption SHOULD describe the identity of the purpose of the table accurately, meaningfully, and concisely.
-
The name/caption of each data table SHOULD be unique within the context of other tables on the same page.
<table>
<caption>Arti's Class Schedule</caption>
...
You could also use the aria-label
attribute, but remember, this would be invisible to sighted users.
<table aria-label="Arti's Class Schedule">
or, use aria-labelledby
<h3 id="tbl_caption">Arti's Class Schedule</h3>
<table aria-labelledby="tbl_caption">
DON'T use the first row to make a "fake" caption
<table>
<tr>
<th colspan="3">Arti's Class Schedule</th>
</tr>
<tr>
<td> </td>
<th scope="col">Monday</th>
<th scope="col">Tuesday</th>
Use Correct Semantics
- Table headers MUST be designated with
<th>
. - Data table header text MUST accurately describe the category of the corresponding data cells.
Simple Tables
Tables that only have one header (column or row) are relatively simple for screen readers. The data in the table is descriptive on its own. No additional markup is necessary to make this table accessible.
Table with one column header
Course | Final Grade |
---|---|
Website Development with HTML5 | A |
Intro to Programming with JavaScript | AB |
<table>
<caption>Semester Grades:</caption>
<tr>
<th>Course</th>
<th>Final Grade</th>
</tr>
<tr>
<td>Website Development with HTML5</td>
<td>A</td>
</tr>
<tr>
<td>Intro to Programming with JavaScript</td>
<td>AB</td>
</tr>
</table>
Table with one row header
Course | Website Development with HTML5 | Intro to Programming with JavaScript |
---|---|---|
Final Grade | A | AB |
<table>
<caption>Semester Grades</caption>
<tr>
<th>Course</th>
<td>Website Development with HTML5</td>
<td>Intro to Programming with JavaScript</td>
</tr>
<tr>
<th>Final Grade</th>
<td>A</td>
<td>AB</td>
</tr>
</table>
Complex Tables
In the above tables the data is self-evident. It's not likely you will think that the letter "A" is a course name. However, some tables may have ambiguous data.
The scope
attribute
The scope
attribute tells the browser and screen reader that everything under the column is related to the header at the top, and everything to the right of the row header is related to that header. It is placed in the th
opening tag.
Tables with ambiguous data
The below table has city names for multiple columns. Using the scope
attribute helps identify what column each city belongs to.
Last Name | Birth Place | Current City |
---|---|---|
Hoang | Phoenix, AZ | Flint, MI |
Smith | San Diego, CA | Madion, WI |
<table>
<caption>Birth City and Current Resident of Team</caption>
<tr>
<th scope="col">Last Name</th>
<th scope="col">Birth Place</th>
<th scope="col">Current City</th>
</tr>
<tr>
<td>Hoang</td>
<td>Phoenix, AZ</td>
<td>Flint, MI</td>
</tr>
<tr>
<td>Smith</td>
<td>San Diego, CA</td>
<td>Madion, WI</td>
</tr>
</table>
Table with row and column headers
Using scope
is especially useful to help screen readers understand content in tables that have column and row headings.
Monday | Tuesday | Wednesday | Thursday | Friday | |
---|---|---|---|---|---|
09:00 - 11:00 | Closed | Open | Open | Closed | Closed |
11:00 - 13:00 | Open | Open | Closed | Closed | Closed |
<table>
<caption>Delivery slots:</caption>
<tr>
<td></td>
<th scope="col">Monday</th>
<th scope="col">Tuesday</th>
<th scope="col">Wednesday</th>
<th scope="col">Thursday</th>
<th scope="col">Friday</th>
</tr>
<tr>
<th scope="row">09:00 - 11:00</th>
<td>Closed</td>
<td>Open</td>
<td>Open</td>
<td>Closed</td>
<td>Closed</td>
</tr>
<tr>
<th scope="row">11:00 - 13:00</th>
<td>Open</td>
<td>Open</td>
<td>Closed</td>
<td>Closed</td>
<td>Closed</td>
</tr>
</table>
Tables with multiple headers for each data cell
To clarify what the headers are for each cell, you can use the colgroup
and rowgroup
values of the scope
attribute.
The below example uses colgroup
for the Intro to Database and Website Development headings.
This table could be split into two tables (Intro to Database grades and Website Development grades). This is a better option than making complex headers in one table.
Intro to Database | Website Development | |||
---|---|---|---|---|
Quiz Score | Exam Score | Quiz Score | Exam Score | |
Jane Hansen | 89 | 98 | 83 | 91 |
Bob Smith | 77 | 81 | 89 | 85 |
<table>
<caption>Grades for Spring</caption>
<tr>
<td rowspan="2"></td>
<th colspan="2" scope="colgroup">Intro to Database</th>
<th colspan="2" scope="colgroup">Website Development</th>
</tr>
<tr>
<th scope="col">Quiz Score</th>
<th scope="col">Exam Score</th>
<th scope="col">Quiz Score</th>
<th scope="col">Exam Score</th>
</tr>
<tr>
<th scope="row">Jane Hansen</th>
<td>89</td>
<td>98</td>
<td>83</td>
<td>91</td>
</tr>
<tr>
<th scope="row">Bob Smith</th>
<td>77</td>
<td>81</td>
<td>89</td>
<td>85</td>
</tr>
</table>
thead
, tfoot
, and tbody
elements
These elements define table headers, footers, and body content. They provide no additional accessibility functionality. However, there is no harm in using them for table styling with CSS. The tbody
element is always added in every table, even if you don't specify it in your code. That being said, it is best to add it to your code as it can provide more control over your table structure and styling.
Item | In-state | Out-of-state |
---|---|---|
Tuition | $8,844 | $17,787 |
Student service fees | $2,028 | $2,028 |
COB graduate level course fee | $1,485 | $1,485 |
Estimated totals | $12,357 | $21,300 |
<table>
<caption>Tuition and fees</caption>
<thead>
<tr>
<th scope="col">Item</th>
<th scope="col">In-state</th>
<th scope="col">Out-of-state</th>
</tr>
</thead>
<tbody>
<tr>
<td>Tuition</td>
<td>$8,844</td>
<td>$17,787</td>
</tr>
<tr>
<td>Student service fees</td>
<td>$2,028</td>
<td>$2,028</td>
</tr>
<tr>
<td>COB graduate level course fee</td>
<td>$1,485</td>
<td>$1,485</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Estimated totals</td>
<td>$12,357</td>
<td>$21,300</td>
</tr>
</tfoot>
</table>
Best Practices
-
Let the window determine the width of the table to reduce horizontal scrolling required with low vision.
-
Use relative values (percentages) to define cell widths
-
Avoid using cell heights so the cell can naturally expand to expose it's content. This is useful for uses that increase the default size of screen text.
-
Avoid using
<br>
inside cells. This may cause data to no longer align correctly when the text is resized. -
Align text data to the left and numeric data to the right so that people using larger text sizes or small screens can find the data easier to read.
-
Apply alternate color backgrounds as styling to tables for a visual guide. Also, highlighting the cell on mouseover and keyboard focus helps orient people to where they are.
-
Make sure there is appropriate contrast between the background color and text.
-
Make sure to separate each piece of data into individual rows. Placing all the data in one cell makes it impossible for screen readers to determine the relationship between data across columns (see below).
Incorrect way to display data
Month | Date & Location |
---|---|
January | 15th: Milwaukee, WI; 21st: Chicago, IL |
February | 6th: New York, NY; 11th: Richmond, VA |
Correct way to display data
Month | Date | Location |
---|---|---|
January | 15th | Milwaukee, WI |
21st | Chicago, IL | |
February | 6th | New York, NY |
11th | Richmond, VA |