Tables for Tabular Data
Use tables for data grids — th/scope, captions, headers, responsive strategies, and when CSS grid is the wrong substitute.
- html
- tables
- a11y
HTML tables exist for tabular data: values where rows and columns both carry meaning. Using tables for page layout is a 1990s habit; skipping tables for real data grids and faking them with divs is a modern accessibility mistake.
Docs: MDN table basics, <table>, WAI tables.
Solid data table
<table>
<caption>Q2 invoice status</caption>
<thead>
<tr>
<th scope="col">Invoice</th>
<th scope="col">Customer</th>
<th scope="col">Amount</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">INV-1842</th>
<td>Globex</td>
<td>$1,200.00</td>
<td>Paid</td>
</tr>
<tr>
<th scope="row">INV-1843</th>
<td>Initech</td>
<td>$840.00</td>
<td>Open</td>
</tr>
</tbody>
</table>
caption— table title (visible or styled)th scope="col|row"— associates headers for ATthead/tbody/tfoot— structure for styling and semantics
Complex headers
<th scope="colgroup" colspan="2">2026</th>
For multi-level headers, use colgroup, headers/id associations when scopes aren’t enough — see WAI complex tables tutorial.
When not to use tables
| Need | Prefer |
|---|---|
| Page columns | CSS Grid / Flex |
| Card layout | Grid of articles |
| Definition pairs | dl sometimes; or simple list |
| Spreadsheet-like data | Table |
Responsive strategies
.table-wrap {
overflow-x: auto;
}
table {
border-collapse: collapse;
width: 100%;
min-width: 36rem;
}
th,
td {
padding: 0.5rem 0.75rem;
text-align: start;
border-bottom: 1px solid var(--border);
}
Options:
- Horizontal scroll wrapper
- Stacked row cards via CSS on small screens (careful with header repetition)
- Priority columns + “details” disclosure
Don’t display: block every cell without restoring header context for AT.
Interactive grids
Sorting, selection, and virtualization often need ARIA grid patterns or established component libraries. Start from a real <table> when the data is tabular; enhance with buttons in headers for sort:
<th scope="col">
<button type="button" aria-pressed="false">
Amount
</button>
</th>
Interview out-loud
“Tables are for row/column data, not layout. I use caption, th with scope, and thead/tbody. Responsive tables usually scroll horizontally or reflow carefully while keeping header associations. Div-based ‘tables’ need heavy ARIA to catch up—prefer native.”
Footguns
- Layout tables.
- Missing headers on every column.
- Empty header cells without labels.
- CSS that breaks table semantics (
display: blockontrwithout a tested pattern). - Putting entire paragraph essays in one cell without structure.
Sortable header sketch
<table>
<caption>Active users</caption>
<thead>
<tr>
<th scope="col">
<button type="button" aria-pressed="true">
Name
<span aria-hidden="true">▲</span>
</button>
</th>
<th scope="col">
<button type="button" aria-pressed="false">Last active</button>
</th>
</tr>
</thead>
<tbody>…</tbody>
</table>
Announce sort changes with a live region if the full table re-renders. Keep keyboard operability on the header buttons. Do not make the entire th a click-only div without a real button or proper grid pattern.
Empty and loading states
<table>
<caption>Team members</caption>
<thead>…</thead>
<tbody>
<tr>
<td colspan="4">No members yet. <a href="/invite">Invite someone</a>.</td>
</tr>
</tbody>
</table>
Keep the table structure when empty so column headers still explain the data model. For loading, prefer skeleton rows with aria-busy on the table container rather than replacing the whole table with a spinner that loses context.
Numeric alignment
td.num {
text-align: end;
font-variant-numeric: tabular-nums;
}
Right-align numbers for comparison; keep headers aligned with their columns. Use scope and clear header text (“Amount (USD)”) so units are not only in CSS-generated content. For downloadable data, offer CSV alongside the table for power users and AT workflows that prefer raw data.
Related
Further reading
Related guides
- Accessibility Tree OverviewHow browsers build the accessibility tree from DOM and CSS — roles, names, states, what’s pruned, and how to inspect it in DevTools.
- Images alt and DecorativeWrite useful alt text, mark decorative images correctly, and avoid SEO/a11y anti-patterns like keyword stuffing or missing alt.
- Landmark Elementsheader, nav, main, aside, footer landmarks — how AT users jump sections, labeling multiple navs, and common landmark mistakes.
- Semantic HTMLPick elements for meaning, not looks — landmarks, headings, forms, media, and how semantics feed a11y and SEO.
- Audio and Video ElementsNative audio/video — controls, sources, captions, autoplay policies, and accessibility requirements for media on the web.