Presenting tabular data
Use a table when the data has rows and columns and the reader needs to compare across and down. Mark the headers, and every cell can tell a screen reader which row and column it belongs to.
In one minute
- Two dimensions? Use
<table>. One column of names is a list. Page layout is CSS, never a table. - Name the table with
<caption>. Mark column headers<th scope="col">and row headers<th scope="row">. - Sortable? Put a real button inside the header, set
aria-sort, and announce the new order without moving focus. - Buttons inside cells need names that include the row: "Edit Northwind", not "Edit".
Picture a spreadsheet read out loud. If the reader says only "21", you have no idea what it means. A good table is a spreadsheet where every number is read with its labels: "Harbor, Points, 21". Headers are those labels, and the screen reader repeats them for you as you move around.
1. Headers give every cell its labels
Browsers list every table in the accessibility tree (the version of the page that screen readers use) with its rows,
columns and header cells. Screen readers offer table keys, such as Ctrl + Alt + arrow keys in
NVDA and JAWS, to move cell by cell. The reader then says the header text as you cross into a new row or column.
Without th there is nothing to say.
Lab: what is heard on one cell
Step 1. Choose how the table is marked up, then pick a cell with the two selects.
Use scope="col" and scope="row" for simple tables. Complex tables with merged cells
(colspan or rowspan) can need headers and id instead, but first ask if
you can split the table into two simple ones, which is easier for everyone. The <caption> is the
table's name: it is read when the user arrives, and it shows up in the screen reader's list of tables (WCAG 1.3.1).
2. Sorting: a button, a state, and a message
A sortable column header holds a real <button>. After a press, three things must be true.
The header says which way it is sorted (aria-sort="ascending" or "descending", on one header only).
A status message announces the change. And focus stays on the button so the user can press again.
Lab: sort the standings
Step 2. Pick the feedback style, then press a column button with Enter or Space.
| Region |
|---|
The visible arrow icons you often see on sortable headers should be hidden from screen readers
(aria-hidden="true") because aria-sort already carries the meaning. Do not make the whole
header row a link. For tables with hundreds of rows, consider paging, and keep the header visible when scrolling.
3. Buttons and links inside cells
Cells can hold controls. Screen reader users often pull up a list of all buttons on the page, and in that list the
surrounding row is gone. Ten buttons all named "Edit" are ten identical entries. Add the row to the name, either with
visually hidden text or aria-label. Keep the visible text inside the name (WCAG 2.5.3 Label in Name).
Lab: the list of buttons
Step 3. Switch the naming. The list shows what a screen reader's "list of buttons" would contain.
| Team | Action |
|---|
4. Table, list or layout?
A table says "compare these". If the data does not have that shape, a table is noise: the screen reader announces "table with 5 rows, 2 columns" and then makes the user navigate it. Layout tables (using a table to position things) are out. Use CSS Grid or Flexbox, which do not add table semantics.
Lab: pick the right structure
Step 4. Choose one situation, then say which structure fits.
Common mistakes
| You see | Why it is a problem | Fix |
|---|---|---|
Header cells made with bold td | Cells have no labels for a screen reader. | th scope="col" / scope="row". |
| No caption | The table has no name in the list of tables. | <caption>, or aria-labelledby pointing at a heading. |
| Sort changes rows silently | Order change is invisible to screen reader users. | aria-sort + status message. |
| Focus moves after sorting | User loses their place and cannot sort again. | Leave focus on the button. |
| Many buttons named "Edit" | Identical entries in a button list. | "Edit Northwind". |
| Table used for layout | Extra announcements, hard to reflow at zoom. | CSS Grid or Flexbox. |
| Wide table breaks at 400% zoom | WCAG 1.4.10 Reflow; data tables may scroll sideways but must stay usable. | Wrap in a scrollable region with a label and tabindex="0". |
- In lab 2, Tab to "Points" and press Enter twice. Did focus stay on the button each time?
- In lab 1, switch to "Column + row th" and read the sentence for each cell. Which one would you want to hear?
- If you have a screen reader on, press its "next table" key on a page of your own and listen to the caption.
Remember
- A table is for comparing along rows and columns. Otherwise use a list or CSS.
caption,th scope="col",th scope="row"give every cell its labels.- Sort with a button in the header,
aria-sort, a status message, and steady focus. - Controls in cells carry the row name.