Skip to content
Pandus

Accessible data tables

Updated October 14, 2026 · 5 min read

Size charts, product specifications, shipping rates, order history, comparison tables — shops have more tables than they think. Screen reader users navigate tables cell by cell and hear the header for each cell, but only if the headers are in the code. This page is part of the accessible UI patterns series.

When to use a table

Use a table when data has a relationship in two dimensions: rows and columns that both mean something (size × measurement, country × shipping cost). Do not use tables for layout, and do not build tabular data from div elements styled as a grid.

The APG table pattern describes a table as a static structure that "is not an interactive widget", and says: "authors are strongly encouraged to use a native HTML table element whenever possible." If many cells contain interactive widgets, the APG grid pattern can reduce the number of tab stops — but a grid is a composite widget with its own arrow-key model and is rarely needed on a shop.

Relevant WCAG criteria

Code example

<!-- Simple table: headers in the first row and first column -->
<table>
  <caption>Size chart for women's jeans, in centimetres</caption>
  <thead>
    <tr>
      <th>Size</th>
      <th>Waist</th>
      <th>Hip</th>
    </tr>
  </thead>
  <tbody>
    <tr><th>S</th><td>66–70</td><td>92–96</td></tr>
    <tr><th>M</th><td>71–75</td><td>97–101</td></tr>
  </tbody>
</table>

<!-- Sortable column: a button in the header, aria-sort on the header cell -->
<th aria-sort="ascending">
  <button type="button">Price</button>
</th>

<!-- Wide table on small screens: a focusable, named scroll container -->
<div class="table-scroll" role="region" aria-label="Shipping rates" tabindex="0">
  <table>…</table>
</div>

Notes:

  • W3C technique H51 is "using table markup to present tabular information". H63 covers scope (row, col, rowgroup, colgroup): needed when headers are not in the first row or column; plain th is enough for simple tables with headers in the first row or column. For complex tables with headers on several levels, H43 uses id and headers.
  • caption names the table. Do not fake it with a bold first row or a heading above that is not connected.
  • Per the APG, aria-sort goes on the header of the sorted column; set it on one column at a time.
  • If rows are loaded in pages or virtualised, aria-rowcount and aria-rowindex tell assistive technology the full size.

Keyboard interaction

The APG lists keyboard interaction for the table pattern as "not applicable": a table is not a widget, and its cells are not focusable. Screen readers have their own table navigation commands. Interactive elements inside cells — links, sort buttons, quantity fields — are ordinary tab stops.

Key Function
Tab Moves to the next link, button or field inside the table
Arrow keys (when the scroll container has focus) Scroll a wide table horizontally
Screen reader table commands Move cell by cell; the header of each cell is announced

Common mistakes on online shops

  • Size charts as images without a text table, or as tables without any th.
  • Specification lists built from div elements that look like two columns but have no structure — use a table or a description list.
  • Layout tables from old email-style templates, marked up with th.
  • Comparison tables with merged header cells and no headers attributes.
  • A first bold row instead of a caption or th.
  • Wide tables that overflow the page on mobile, or a scroll container that keyboard users cannot reach.
  • Sort links without state, so users do not know the current order.

What automated checks catch vs manual testing

Automated (axe-core rule) What it finds
td-has-header Data cells in larger tables without a header
th-has-data-cells Header cells that describe no data cells
td-headers-attr headers attributes that point outside the table
table-fake-caption A first row used as a caption
empty-table-header Header cells with no text (advice)
scope-attr-valid Invalid scope values (advice)
table-duplicate-name Caption and summary with the same text (advice)
scrollable-region-focusable A scrolling container keyboard users cannot reach

All are cards in our rule library. Manual testing: decide whether each table is data or layout, check that headers are correct and meaningful, navigate with a screen reader's table commands, and check the table on a 320 CSS pixels wide screen.

Run the free one-page check on a product page with a size chart. It is an automated check, not a full audit.

Frequently asked questions

How do I make a table accessible?

Use a real HTML table for tabular data, mark header cells with th, add scope where headers are not simply in the first row or column, give the table a caption, and do not use tables for layout.

When do I need the scope attribute?

W3C technique H63 says simple tables with headers in the first row or column work with plain th elements; scope helps when headers are elsewhere. For complex tables with multi-level headers, use id and headers attributes (H43).

Should I use role="table" or role="grid"?

Prefer the native table element; the APG strongly encourages it whenever possible. A grid is an interactive composite widget with arrow-key navigation, useful only when a table contains many interactive cells.

How do I show which column is sorted?

Put aria-sort="ascending" or "descending" on the header of the sorted column, and make the sort control a button inside the header cell.

Can an automated check test tables?

It finds data cells without headers, headers without data cells, broken headers attributes, invalid scope values and fake captions. It cannot tell whether a table is really a data table or whether the headers make sense.

Sources

  • W3C WAI, APG Table pattern: https://www.w3.org/WAI/ARIA/apg/patterns/table/
  • W3C, Technique H63 Using the scope attribute: https://www.w3.org/WAI/WCAG22/Techniques/html/H63
  • W3C WAI, APG Grid pattern: https://www.w3.org/WAI/ARIA/apg/patterns/grid/
  • W3C, Understanding 1.4.10 Reflow: https://www.w3.org/WAI/WCAG22/Understanding/reflow.html