This week’s question comes from Luís Osório.
If I have a form, and I want to collect tabular data, I would like to make a table with th for row and column headers and then, in each td I would like to have an input (type=text). The thing is … an input must have a label and each td should reference the th. How should this be done using a table?
Your situation has two distinct accessibility requirements: table cells should be associated to their respective row and/or column headers, and inputs need descriptive labels. We’ll address these individually.
Data Table Accessibility
First, you might ask whether the table really is a data table at all, or whether it is primarily being used to lay out and position the data entry fields. It is most likely that users will navigate from form field to form field, opposed to navigating the table structure. So, adding the table accessibility markup is not likely to have a notable impact.
But it’s easy to add. Simply identify each column header cell with
<th scope="col"> (as opposed to
table data cells):
If you have row headers, use
<th scope="row">. Just make sure that you don’t have empty
th elements, and that empty or data cells always use
td. That’s it for the table.
For the form, label the inputs by associating a
label element to an input based on the
input’s id attribute as follows:
<label for="fname">First Name:</label> <input id="fname" type="text">
This works great for most inputs that have a visual label adjacent to them. But for your data entry fields, it’s most likely that one text item (probably your column header) will be used to visually label multiple inputs. Because the id attribute must be unique per page (e.g., you can’t have two elements that each have
id="fname" on one page), this creates a one–to–one relationship between form label and input.
Labelling Multiple Fields
While we can’t label multiple inputs using one
label in a column header, there are several approaches to ensure accessibility. One approach would be to use an off-screen
label element for each
input. Simply add the associated
label adjacent to each control in your HTML, but hide the
label element off-screen using CSS. Sighted users will see the column header, and screen reader users will hear the associated off-screen
label text. This works well, but requires a lot of additional markup.
Alternatively, you could use a descriptive
title attribute value on each
input. Screen readers generally ignore the
title attribute, but will read it for inputs that do not have an associated
label. This will also generate a mouse tooltip on the
input, which may or may not be useful.
As a final, and perhaps optimal, approach, you could use the
aria-labelledby attribute to overcome the one–to–one
input limitation. The
aria-labelledby attribute goes on the
input itself and references the element(s) on the page (based on
id attribute values) that contains the input’s
label text. The screen reader will read the text within the associated element as the
label text when it encounters the input:
For your data entry form, each
table header would have a unique
id value. Then each
input in that header’s row or column would reference that header as an ARIA label. You can even have multiple
aria-labelledby values for each
input — they will be read in the order listed. This is particularly helpful with data entry.
<table> <tr> <th scope="col">Name</th> <th id="agelabel" scope="col">Age</th> <th id="phonelabel" scope="col">Phone</th> </tr> <tr> <th id="jared" scope="col">Jared</th> <td><input type="text" size="3" name="age1" aria-labelledby="jared agelabel"></td> <td><input type="text" size="12" name="phone1" aria-labelledby="jared phonelabel"></td> </tr> <tr> <th id="luis" scope="col">Luís</th> <td><input type="text" size="3" name="age2" aria-labelledby="luis agelabel"></td> <td><input type="text" size="12" name="phone2" aria-labelledby="luis phonelabel"></td> </tr> <tr> <th id="aaron" scope="col">Aaron</th> <td><input type="text" size="3" name="age3" aria-labelledby="aaron agelabel"></td> <td><input type="text" size="12" name="phone3" aria-labelledby="aaron phonelabel"></td> </tr> </table>
Each input in this case would have
aria-labelledby values that reference both the column and row header cells based on their
id attribute values:
<input aria-labelledby="jared agelabel" type="text">
This final approach has great support in all modern browsers and screen readers, and it provides great flexibility when dealing with complex data entry forms.