When <table> is the right element
Use <table> when you have data with a meaningful row-and-column
relationship: each cell relates to its row’s identifier and its
column’s identifier. Pricing matrices, browser compatibility
charts, sports league standings, financial statements.
Do not use <table> for visual layout. CSS Grid and Flexbox
(/css/layout-primitives) replace every
historical layout-table use case. Layout tables harm screen-reader
users (who hear “table with N rows” announcements that are
meaningless) and break responsive design.
About 6% of pages on the HTTP Archive 2024 dataset
still use <table> for layout, down from 32% in 2014.
Required structure
A robust data table:
<table>
<caption>Browser engine support, Q1 2025</caption>
<thead>
<tr>
<th></th>
<th scope="col">Chromium</th>
<th scope="col">Gecko</th>
<th scope="col">WebKit</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">CSS :has()</th>
<td>Yes</td>
<td>Yes</td>
<td>Yes</td>
</tr>
<tr>
<th scope="row">View transitions</th>
<td>Yes</td>
<td>Yes</td>
<td>Partial</td>
</tr>
</tbody>
</table>
The pieces that matter:
<caption>— the table’s name. Announced first by screen readers. Always provide.<thead>and<tbody>— improve programmatic structure and enable repeating header rows during print.<th scope="col">— declares “this is a column header”. The column header text is then associated with each<td>below it.<th scope="row">— declares “this is a row header”. Useful when each row is identified by its first cell.<th>is not the same as<td>.<th>exposesrole="rowheader"orrole="columnheader".
Cells with multiple headers
When a cell relates to more than one row or column header
(common in financial statements), use headers and id:
<table>
<caption>Revenue by region and quarter</caption>
<tr>
<td></td>
<th id="q1">Q1</th>
<th id="q2">Q2</th>
</tr>
<tr>
<th id="emea">EMEA</th>
<td headers="emea q1">42M</td>
<td headers="emea q2">48M</td>
</tr>
</table>
The headers attribute references ids; assistive technology
announces both header names plus the cell value.
Sortable tables
For sortable column headers, add <button> inside the <th>:
<th scope="col" aria-sort="ascending">
<button type="button">
Year <span aria-hidden="true">▲</span>
</button>
</th>
aria-sort accepts none, ascending, descending, other. Set
on the <th> (not the <button>) so screen readers announce the
column’s current sort state on every cell.
Responsive patterns
Tables resist responsive design because their structure assumes horizontal real estate. Three common approaches:
- Horizontal scroll. Wrap the table in a container with
overflow-x: autoandtabindex="0". Pros: structure preserved. Cons: thumb scroll on mobile is awkward. - Stacked rows. On narrow viewports, each row becomes a
key-value list. Implementations vary in accessibility; the
well-tested pattern uses CSS only with
data-labelattributes on cells. - Disclosure rows. Show only the most important columns;
reveal the rest in an expandable detail row. Implemented with
<details>inside<td>or with JavaScript.
About 35% of responsive sites surveyed in 2024 use horizontal scroll, 50% use stacked rows, and 15% use disclosure rows.
Common pitfalls
<table>for layout. Use CSS Grid.- Missing
<caption>. The table has no announced name. - Missing
scopeon<th>. Some engines guess; behaviour is inconsistent. Always specify. <thead>for visual styling only. If the row contains data, not headers, do not wrap in<thead>.role="grid"on a static data table.role="grid"adds an expectation of a 2-D keyboard model (arrow-key navigation cell to cell); add it only if you implement that model.- Sticky header row without
position: stickyonth. Usethead th { position: sticky; top: 0; }to keep the header visible during long-table scroll.
Cross-engine support
<table> and its descendants reached interop two decades ago.
Modern features:
position: stickyon<th>reached interop in 2017 across Chromium, WebKit, and Gecko.aria-sortmappings reached interop in 2018.display: contentson<tr>(used in CSS-led restructuring) reached interop in 2020 but disables the row’s role in some engines; use carefully.
Further reading
- HTML Living Standard, §4.9 Tabular data.
- Adrian Roselli’s Tables, CSS Display Properties, and ARIA
is the reference for the
display: contentsinteraction. - Heydon Pickering’s Inclusive Components: A Responsive Table walks the responsive patterns end-to-end.