Accessible data tables: headers, captions and scope
An accessible data table uses real table markup: a th for every header cell, a scope of col or row on each one, and a caption that names the table. That lets a screen reader say the right headings as someone moves from cell to cell. Keep tables simple, and use spans, groups or id and headers only when a table cannot be split.
Published . Last reviewed .
A data table organizes information that has a logical relationship in a grid, such as a schedule or a feature comparison. A sighted reader scans for the column heading above and the row label to the left. W3C's Tables Tutorial says visual cues alone are not enough: screen reader users hear the matching row and column headers as they move through the cells, but only when the table is marked up that way.
The requirement is WCAG success criterion 1.3.1 Info and Relationships (Level A): relationships shown visually must be programmatically determined, meaning readable from the code, or available in text. W3C's Understanding page for 1.3.1 lists table markup, captions, scope and id with headers as sufficient techniques, and lists failures for getting them wrong (F91, F90, F46 and F48, all in the table below). Not every part is required in every table: W3C treats scope as optional in a simple table and a caption as not always needed, and we recommend both anyway.
Headings outside tables are in our heading structure guide.
What an accessible table needs
| Part | Markup | W3C reference |
|---|---|---|
| The table | table, tr, th and td, not tabs, br or pre | H51; fails: F48; Tips and Tricks |
| Header cells | th for every column and row heading, with text in it | Fails when missing: F91 |
| Header direction | scope="col" or scope="row" on each th | H63 |
| The title | caption as the first child of table | H39 |
| Headers that span | colspan or rowspan, with scope="colgroup" or scope="rowgroup" | Irregular headers tutorial |
| Relationships too complex for scope | id on each header and headers on each data cell | H43; fails if wrong: F90 |
| A table used only for layout | None of the above; use CSS, or role="presentation" on an existing layout table | Fails with th or caption: F46 |
Start with real table markup
W3C's technique H51 says to present tabular information with table, tr, th and td. It notes that visual methods such as tabs or the pre element do not convey row and column relationships to people who cannot see the layout. F91 also accepts the ARIA columnheader and rowheader roles, but we prefer native elements, which leave less to get wrong.
Three habits that W3C's Tables Tips and Tricks warns against:
- A
tdstyled to look like a header. Usethinstead. - A
brto fake a second row. The alignment breaks when text is resized. - A header row in the middle of a table to start a new topic. Screen readers may read all the headers in a column together, so start a new table.
PDF and word processor files have their own table markup, which W3C's Tables Tutorial says is often lost when a file is converted (see our accessible PDF guide). The tutorial adds that many web authoring tools and content management systems can define header cells as you create a table. Use that option, then inspect the markup it produces.
Header cells and the scope attribute
A th is a header cell and a td is a data cell. Give every header cell text (WebAIM says headers should never be empty) and no heading elements (MDN says a th cannot contain them).
The scope attribute says which cells a header applies to. When it is missing or invalid, MDN says browsers choose the cells automatically.
| Value | The header applies to |
|---|---|
col | All cells in its column |
row | All cells in its row |
colgroup | All cells in its column group, for a header that spans several columns |
rowgroup | All cells in its row group, for a header that spans several rows |
Does every th need a scope? W3C's technique H63 says plain th elements are sufficient in a simple table with headers in the first row or column. MDN says scope is redundant there because it is inferred, though some assistive technology may infer it wrongly. W3C's one-header tutorial notes that when the direction is ambiguous, some screen readers read all of a row's headings, such as Date, Event and Venue, in the Venue cell. WebAIM says all th elements should generally have a scope. We follow WebAIM: it costs a few characters and removes the guesswork.
In a table with headers in both directions, W3C's two-header example leaves the top-left cell as an empty td, which also avoids an empty header. The example below uses invented data.
<table>
<caption>Room bookings for the week</caption>
<thead>
<tr>
<td></td>
<th scope="col">Monday</th>
<th scope="col">Tuesday</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Room A</th>
<td>Free</td>
<td>Booked</td>
</tr>
<tr>
<th scope="row">Room B</th>
<td>Booked</td>
<td>Free</td>
</tr>
</tbody>
</table>
A screen reader can then say "Monday" and "Room A" at the first "Free". Links inside cells need care too: a column of identical "Edit" links gives every row the same link text, which our accessible link text guide shows how to fix.
Captions and summaries
A caption works like a heading for a table. W3C's caption and summary tutorial says most screen readers announce it, and that it helps users find a table, understand its topic and decide whether to read it. Make it the first child of the table element (MDN). If the table has a visible title, put that text in the caption: W3C's technique H39 tests for exactly that, and it ties the title to the table.
W3C notes that a caption is not required in every case to meet WCAG, and its Tables Tutorial calls a caption useful in most situations. In our guides every data table has one, kept short and specific: "Room bookings for the week" beats "Table 1".
A summary describes how the data is organized. W3C says it is usually needed only for complex tables and should not repeat the caption. It can go inside the caption, or in a paragraph linked with aria-describedby, which W3C notes may be less widely supported. A table wrapped in a figure can use figcaption, but W3C notes that screen reader users navigating by table usually cannot identify the table from it alone, so the table needs aria-labelledby as well. In our reading a plain caption is simpler. Do not use the summary attribute on table: the HTML standard lists it as obsolete.
Complex tables: spans, groups and multi-level headers
Most tables need nothing beyond the markup above.
Spanning headers
W3C's irregular headers tutorial makes a header cover several columns or rows with colspan or rowspan. A header over several columns gets scope="colgroup", one over several rows gets scope="rowgroup", and single headers keep col and row. The groups have to exist first: the tutorial groups columns with colgroup and rows with thead, tbody or tfoot, and says the columns counted by col elements and span attributes should equal the table's column count. The example uses invented data.
<table>
<caption>Room bookings by time of day</caption>
<colgroup></colgroup>
<colgroup span="2"></colgroup>
<colgroup span="2"></colgroup>
<thead>
<tr>
<td rowspan="2"></td>
<th scope="colgroup" colspan="2">Morning</th>
<th scope="colgroup" colspan="2">Afternoon</th>
</tr>
<tr>
<th scope="col">Room A</th>
<th scope="col">Room B</th>
<th scope="col">Room A</th>
<th scope="col">Room B</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Monday</th>
<td>Free</td>
<td>Booked</td>
<td>Free</td>
<td>Free</td>
</tr>
</tbody>
</table>
WebAIM says thead, tfoot and tbody provide no accessibility functionality of their own and are mainly useful when a long table is printed, and MDN describes them as a way to add structure and styling control. WebAIM adds that some screen readers still do not fully support spanned and multi-level headers, so flatten a table where you can.
Id and headers
W3C's multi-level headers tutorial lists two kinds of table to mark up with id and headers: tables whose column headers repeat or change part-way down, and tables where a data cell has three or more headers. Each header gets a unique id, and each data cell lists its headers' ids, separated by spaces:
<th id="morning" scope="colgroup" colspan="2">Morning</th>
<th id="room-a-am" scope="col">Room A</th>
<th id="monday" scope="row">Monday</th>
<td headers="monday morning room-a-am">Free</td>
W3C's tips say complex tables are more work for the people who write them and harder for readers to interpret, MDN says the method uses a lot more markup and leaves no room for errors, and WebAIM calls it rarely necessary. A wrong reference breaks the link quietly: W3C's H43 tests that every id in a headers attribute matches a header cell, every data cell lists all its headers and every id is unique, and F90 is the failure for getting it wrong.
Consider splitting the table first: the same W3C tips say it is usually better to break a complex table into simple tables that each hold the data for one sub-topic.
Tables used for layout
W3C says tables are not meant for layout and recommends CSS. F46 fails a layout table that uses th elements, a caption or a non-empty summary attribute. For an existing layout table, W3C's tips say to add no headers or caption and to set role="presentation".
Wide tables, zoom and small screens
A wide table has to scroll somewhere. WCAG 1.4.10 Reflow (Level AA) asks for content without two-dimensional scrolling at 320 CSS pixels wide, with an exception for parts that need a two-dimensional layout, which W3C's Understanding page says includes data tables. The exception covers only the table. Headings and controls around it, and the content of each cell, must still reflow, which is why W3C's examples put the table in its own scrollable container. Three details matter:
- Scroll, do not clip. W3C's tips warn against cutting a table off with
overflow: hidden, and its tutorials putoverflow: scrollon a wrapper instead. - Make it reachable by keyboard. Keyboard users scroll a region with the arrow keys only when focus is on or inside it. The W3C ACT rule for scrollable content passes an element with
tabindex="0"and fails one with no focusable element on or inside it. It also notes that some browsers make scrollable elements focusable automatically, so we addtabindex="0"rather than depend on that. - Name it. On our own pages each wrapper also has
role="region"and takes its name from the caption, so a screen reader says what has focus. That is our choice, not a W3C requirement; see landmarks and regions and focus order and tabindex.
<div role="region" aria-labelledby="bookings-caption" tabindex="0">
<table>
<caption id="bookings-caption">Room bookings for the week</caption>
...
</table>
</div>
W3C's tips add three more rules. If a responsive design changes a table's format, the row and column relationships must stay clear in every format. Left-align text and right-align numbers. Keep text contrast sufficient in both header and data cells (see 1.4.3 Contrast (Minimum)).
Check it yourself
A tool can see that a th exists, but not whether the first row really is the header, so a person has to look.
- Read the markup. Inspect each table. Every header should be a
thwith ascopeofcol,row,colgrouporrowgroup, which is the H63 test. The caption should be the first child, and a layout table should have none of these. - Listen to it. In NVDA, a free screen reader for Windows, press T for the next table and hold Ctrl and Alt with the arrow keys to move cell by cell. WebAIM's NVDA guide says proper row and column headers are read automatically. Each cell should arrive with the headings a sighted reader would look up. See how a screen reader reads a page.
- Narrow the window. Make it 320 CSS pixels wide, or zoom a 1280-pixel window to 400%, which W3C's Reflow page says is equivalent. Only the table's own container should scroll sideways. Press Tab: focus should reach that container, and the arrow keys should scroll it.
None of GotAlt's tools for web pages reports a missing th, scope or caption, but they cover the page around a table:
- Accessibility bookmarklet checks the page open in your browser for alt text, headings, link and button names, form labels and text contrast, which covers table cells like any other text. It does not check table structure.
- WCAG audit checklist lets you track all 55 Level A and AA criteria, including 1.3.1 and 1.4.10, and export a CSV file.
- PDF checker flags tables with no header cells in a PDF, as an advisory. It does not read web pages.
- Scan my site for free runs 16 rule-based checks on up to 3 pages. None is about tables; what we check lists them.
For automated table rules, axe-core, the open-source engine behind many scanners, includes the rules th-has-data-cells and td-headers-attr, both tagged to 1.3.1, plus the best-practice rules scope-attr-valid and empty-table-header. A clean result from any scanner built on it means only that those checks found nothing: a header that is marked up correctly but says the wrong thing passes every one of them.
Accessible table questions
How do you make a table accessible?
Use a real table element with a th for every header cell, give each th a scope of col or row, and add a caption that names the table. Keep the table simple, never leave a header cell empty, and put a wide table in its own scrollable container that keyboard users can reach.
What is the scope attribute in an HTML table?
The scope attribute on a th says which cells that header applies to: col for its column, row for its row, and colgroup or rowgroup for a header that spans several columns or rows. If it is missing, browsers choose the cells automatically, as MDN's th reference explains.
Do tables need a caption for accessibility?
Not in every case. W3C's caption and summary tutorial says captions are not required in every case to meet WCAG, and its Tables Tutorial calls a caption useful in most situations. A caption works like a heading, so screen reader users can find the table and decide whether to read it. We recommend one on every data table, as the first child of the table element.
What is the difference between a table caption and a summary?
A caption names the table, like a heading. A summary describes how the data is organized, so readers know what sits in each row and column; W3C says it is usually needed only for complex tables and should not repeat the caption. The summary attribute on table is obsolete in HTML, so put summary text in the caption or link it with aria-describedby.
When should I use headers and id attributes in a table?
Rarely. W3C's multi-level headers tutorial uses them for tables whose column headers repeat or change part-way down, or where a data cell has three or more headers, and WebAIM calls them rarely necessary because scope is usually enough. Give each header a unique id and list the matching ids in each data cell's headers attribute. Try splitting the table into simpler ones first.
Can I use a table for page layout?
W3C says tables are not meant for layout and recommends CSS instead. If an existing layout table has to stay, do not add th, caption or headers to it, and add role="presentation" to the table element. WCAG failure F46 covers th and caption in layout tables.
How do I make a wide table work on a phone?
Put the table in a wrapper that scrolls sideways, using overflow: scroll rather than hidden, and give the wrapper tabindex="0" so keyboard users can scroll it. WCAG 1.4.10 Reflow lets a data table need two-dimensional scrolling, but the content around it must still reflow. If the table changes format on small screens, its row and column relationships must stay clear.
How do I make a sortable table accessible?
Put the header text of each sortable column in a button inside the th, and set aria-sort to ascending or descending on the th of the column currently sorted, moving it when the sort changes. The ARIA Authoring Practices Guide example shows this, but calls itself illustrative, so test with assistive technology.
Scan the part a tool can read
The free scan runs 16 rule-based checks on up to 3 pages of your site, including page titles, link and button names, form labels and heading levels. It has no table check, so use the test above for header cells, scope and captions. No signup.
Scan my site for freeRelated guides
- Heading structure: the outline a screen reader and a search engine read.
- Breadcrumbs and navigation: navigation a screen reader can follow.
- Focus order and tabindex: a logical order, and what tabindex does to it.
- The lang attribute: why it matters and how to set it.
- Skip links: how to add one and when it is required.
- Accessible link text: link text that says where it goes.
- Landmarks and regions: header, nav, main and footer for screen readers.
- Page titles: what WCAG 2.4.2 asks for.