I remember a couple years ago speaking at An Event Apart and mentioning how I don't use the definition list. I may have said it a few times, to the annoyance of the audience. When pushed on why I don't use definition lists, I mentioned that screenreaders read out that a term is equal to a definition. Somebody tested and confirmed with me afterwards that his particular screenreader did in fact read it out in a way that would be awkward if people were to use them. (Alas, I can't remember exactly what it was that the screenreader actually announced.)
It's been a couple years since that fateful day and the situation came up at work. A co-worker was using a definition list and I mentioned the accessibility issue. A table was better. But was it? HTML5 indicates that definition lists are now data lists and can take any name/value content. Based on the spec, my co-worker was using the definition list correctly.
I decided to test which is better: a
<table>. In these tests, I'm using the only two screenreaders that I have available to me: VoiceOver (with Chrome 17 on Mac and Safari 5.1, both on Lion) and NVDA (Firefox 11 on Windows 7 via VMWare Fusion).
The design is pretty typical: properties on the left, values on the right.
The Definition List
I created a simple definition list.
<dl> <dt>Property</dt> <dd>Value</dd> <dt>Name</dt> <dd>Val</dd> </dl>
NVDA'S behaviour here is interesting in that it just reads the items without announcing that it's a list or that the terms and definitions have any relation. The content is just read in a linear fashion.
VoiceOver is better in that it informs you that it's a list but it says it is a list with 4 items. That's not very helpful. It doesn't establish any connection between the terms and their definitions.
Based on this alone, I'd say that the DL isn't a great idea. So, what of the table?
I created a simple table with two rows and two columns.
<table> <tbody> <tr> <td>Property</td> <td>Value</td> </tr> <tr> <td>Name</td> <td>Val</td> </tr> </tbody> </table>
NVDA nor VoiceOver provide any insight that this is a table. Once again, the content is simply linearized. This makes sense since tables are often used for layout instead of for semantics.
There are a few different approaches that can be used to inform the screenreader that it's a valid table and allow for a richer interaction experience.
Use header cells
Changing the keys to use a TH instead of a TD allowed the screenreaders to recognize that it was a table and navigate the table.
<table> <tbody> <tr> <th>Property</th> <td>Value</td> </tr> <tr> <th>Name</th> <td>Val</td> </tr> </tbody> </table>
Interestingly, although the table could be navigated and you'd have a sense of rows and columns, neither NVDA nor VoiceOver in Chrome would announce that a particular cell was a header cell. VoiceOver in Safari 5.1, however, would announce the value of the header cell when switching rows. "row 1 of 2 property value" with a long enough pause between the words property and value so as to be able to tell that it's describing the heading for the cell.
For NVDA, adding a scope attribute to the header cell would allow the heading to be announced.
<table> <tbody> <tr> <th scope="row">Property</th> <td>Value</td> </tr> <tr> <th scope="row">Name</th> <td>Val</td> </tr> </tbody> </table>
In this case, NVDA would say "property row 1 of 2 value".
The third approach I used was to specify the ARIA grid role to the table. This also required adding the row roles to each of the TRs.
<table role="grid"> <tbody> <tr role="row"> <th>Property</th> <td>Value</td> </tr> <tr role="row"> <th>Name</th> <td>Val</td> </tr> </tbody> </table>
Once again, NVDA and VoiceOver would read the cells but did not indicate any association between the header cell and the content cell. There are additional roles that can be applied on the cell—
role="rowheader"—but neither provided any additional clarity to the screenreader.
The last technique to inform the screenreaders that the table was a table was to apply a caption to the table.
<table> <caption>Caption</caption> <tbody> <tr> <td>Property</td> <td>Value</td> </tr> <tr> <td>Name</td> <td>Val</td> </tr> </tbody> </table>
What I noticed was that the caption could be hidden using display:none and the table would still be navigable in VoiceOver. Unfortunately, with NVDA, setting the caption to display:none (or even just position:absolute) seems to render the table like a layout table. That's not good.
In testing other options, the only other alternative that I could find that seemed particular relevant was to identify the heading. This could be done in one of two ways: use a heading tag or use a heading aria role.
With the heading tag, the heading level is announced.
With the heading ARIA role, it'll announce "heading level 0" in VoiceOver but just "heading" in NVDA. The document outline should be smart enough to figure out that the ARIA heading. However, in neither Safari nor Chrome was it able to automatically figure it out. Specifying an aria-level attribute should help clarify things. Unfortunately, while Safari recognized this attribute, Chrome didn't. Finally, NVDA recognized and read out "heading level 2" correctly.
Of all the options, I'd use one of two approaches:
- Use a table with scope attributes on header cells; or
- Use an ordered or unordered list (but not a definition list) with headings (tags or ARIA)
I like the way that lists are announced and would therefore lean in that direction. Tables offer a lot of features but for a simple list of name/value pairs, a table feels like too much. Here is how I would code up the example:
<ul> <li><h3>Property</h3> <div>Value</div> </li> <li><h3>Name</h3> <div>Val</div> </li> </ul>
Headings get floated to the left, divs get floated to the right, and list items clear floats. (Of course, use the proper heading level that is appropriate for your document.)
This was an interesting process to explore and I definitely have changed the way I would approach this in the future. Those with insights about other screenreaders are welcome to chime in.