The Problem
I noticed some interesting behaviour with VoiceOver 3 when working with data tables whose first cell in the first, or header, row is an empty td
element. In these cases, VoiceOver does not correctly associate data cells with their proper column th
header cells. Instead, VoiceOver seems to shift the header cells one column to the left, such that it will identify a data cell in the second column of the table as belonging to the third column. That is, it will read the th
element from the third column as the header for a data cell in the second column. Obviously, this can lead to some serious confusion for the user. Considering the fact that JAWS 11, NVDA 2010.2beta1, and Window-Eyes 7.2 don't exhibit this behaviour makes me think it's a bug with VoiceOver.
An Easy Solution
Fortunately, this is pretty easy to rectify by using a th
element instead of a td
element for the empty first cell in the header row. Explicitly adding the thead
and tbody
elements also seems to solve the problem. Update (22 August 2012): Explicity adding thead
and tbody
solves the issue regarding column headers, but introduces some problem behaviour with row headers. In this case, after moving the VoiceOver cursor to a row header in the first column, VoiceOver (at least in Mountain Lion) will announce the row header cell below it seemingly as the column header for the current row header cell.
What is the Correct Markup in Any Case?
Examples from the Accessibility Community
Looking around the web, it's quite easy to find examples of both, where either a td
or a th
element is used for an empty first cell in a header row. In fact, on a good number of pages that specifically address the creation of accessible data tables, the use of td
is fairly common. You can find examples of these at 456bereastreet.com, WebAIM.org, jimthatcher.com, and usability.com.au. Each of these sites is well-respected in the web accessibility community for its expertise, so it is reasonable to assume that the use of a td
element for an empty first cell in a header row is quite proper, if it doesn't also benefit accessibility in some way that I'm not aware of.
Examples from the W3C
On the other hand, many of the relevant examples provided by the W3C, for instance, in Tables in HTML documents from the HTML 4.01 specification, section 4.9 Tabular data from the HTML5 specification, and HTML Techniques for Web Content Accessibility Guidelines 1.0, suggest the use of a th
element for the empty first cell in a table. At the same time, WCAG 2.0 Technique H51: Using table markup to present tabular information does the opposite and suggests the use of an empty td
element.
No Absolute Answer
So there's some variation out there, and it's not clear to me if only one or both approaches are officially correct. We do know that under the HTML 4.01 specification on th
and td
elements, td
is for data cells, while th
is for header cells, and where a cell acts as both header and data, td
is to be used. But what is the status of an empty cell that occurs as the first cell in a row of otherwise header cells? Since it is empty, it is not really acting as a column header, but nor is it providing any data, and it certainly is not both.
If I had to choose, and I'm not suggesting that such a choice is necessary, I'd probably come out in favour of using a th
element, the issue with VoiceOver notwithstanding. This is because, even if empty, the cell exists in a row of column headers, and not data cells, so it seems reasonable and consistent to consider the empty cell as simply an empty column header.
In the end, however, I'm not sure it really matters, except now for the fact that, bug or not, VoiceOver 3 has a problem when td
is used for an empty first cell in a header row, particularly in very simple data tables.
Navigating Tables with VoiceOver
To navigate tables using VoiceOver, press Control+Option+Command+T to move from table to table down the page. Add the Shift key to the previous sequence to move up the page to the previous table. Once you've reached a table, press Control+Option+Shift+Down Arrow to start interacting with it, at which point pressing Control+Option with the arrow keys can be used to move up, down, left, and right through the table cells. To hear the column header for each cell, move left or right through the row.
The Test Tables
In each of the following seven tables there are three columns. The first contains people's names, but the header cell is empty. The second and third columns are for phone number and city name, and their column headers are simple th
elements. The first cell in each of the data rows is also a th
element. Otherwise, the tables vary in the markup used for the first cell in the header row, the use of thead
and tbody
elements, and of the scope
, id
and headers
attributes.
Of the seven variations presented, only two seem to enable VoiceOver 3 to properly associate column headers with the data cells. These are Test Table #3, which uses a th
element for the empty header cell, and Test Table #5, which implements the thead
and tbody
elements. In the remaining versions, VoiceOver will identify the cells in the first column, which contain people's names, as having the header "Phone#". The phone number data cells will be associated with the header "City", and city data cells will have no associated header announced.
Note: The tables were tested with VoiceOver 3 in Safari 5.02. Content for the tables is borrowed from the example at http://www.w3.org/TR/WCAG-TECHS/H63.html.
Test Table #1
- First cell in header row is an empty
td
- Does not use
thead
ortbody
- VoiceOver does not properly associate column headers with data cells
Phone# | City | |
---|---|---|
Joel Garner | 412-212-5421 | Pittsburgh |
Clive Lloyd | 410-306-1420 | Baltimore |
Test Table #2
- First cell in header row is a
td
with a
- Does not use
thead
ortbody
- VoiceOver does not properly associate column headers with data cells
Phone# | City | |
---|---|---|
Joel Garner | 412-212-5421 | Pittsburgh |
Clive Lloyd | 410-306-1420 | Baltimore |
Test Table #3
- First cell in header row is an empty
th
- Does not use
thead
ortbody
- VoiceOver does properly associate column headers with data cells
Phone# | City | |
---|---|---|
Joel Garner | 412-212-5421 | Pittsburgh |
Clive Lloyd | 410-306-1420 | Baltimore |
Test Table #4
- First cell in header row is an empty
td
- Uses
tbody
, but notthead
- VoiceOver does not properly associate column headers with data cells
Phone# | City | |
---|---|---|
Joel Garner | 412-212-5421 | Pittsburgh |
Clive Lloyd | 410-306-1420 | Baltimore |
Test Table #5
- First cell in header row is an empty
td
- Uses
thead
andtbody
- VoiceOver does properly associate column headers with data cells
Update (22 August 2012): Note that explicity adding thead
and tbody
causes issues with row headers in VoiceOver (tested in Mountain Lion with Safari 6.0) where it will sometimes read the row header from the following row as if it were the column header for the current row reader cell. So this is not really a workable solution.
Phone# | City | |
---|---|---|
Joel Garner | 412-212-5421 | Pittsburgh |
Clive Lloyd | 410-306-1420 | Baltimore |
Test Table #6
- First cell in header row is an empty
td
- Uses
scope="col"
on the "Phone#" and "City" column header cells - Does not use
thead
ortbody
- VoiceOver does not properly associate column headers with data cells
Phone# | City | |
---|---|---|
Joel Garner | 412-212-5421 | Pittsburgh |
Clive Lloyd | 410-306-1420 | Baltimore |
Test Table #7
- First cell in header row is an empty
td
- Uses
id
andheaders
attributes to associate data cells in the "Phone#" and "City" columns with their respective column header cells - Does not use
thead
ortbody
- VoiceOver does not properly associate column headers with data cells
Phone# | City | |
---|---|---|
Joel Garner | 412-212-5421 | Pittsburgh |
Clive Lloyd | 410-306-1420 | Baltimore |