Layout Tables Versus Data Tables: When to Use role=“presentation”
Permalink to "Layout Tables Versus Data Tables: When to Use role=“presentation”"A layout table uses <table> markup to position content on screen; a data table uses it to express relationships between values and their headers. The distinction matters because browsers expose the two differently: a data table gets table navigation commands, row and column announcements, and a place in the screen reader’s table list, while a layout table should get none of that. When the browser guesses wrong — and it does guess — users either lose the structure they need or wade through structure that means nothing.
This page covers how browsers make that guess, how role="presentation" overrides it, and how to make a real data table unambiguous. It extends semantic HTML table construction.
Spec reference
Permalink to "Spec reference"HTML-AAM does not define the layout-table heuristic; each browser engine implements its own. Chromium and Firefox both consider a table “layout” when it has no <th>, no <caption>, no <thead>, a single row or column, nested tables, or very few cells, and consider it “data” when it has header cells, a caption, borders on cells or many rows. The heuristic is a repair mechanism for the old web, and you should never rely on it for anything you author.
ARIA 1.2 defines role="presentation" and its synonym role="none" as removing the implicit semantics of an element. On a <table>, the presentational role propagates to the required owned elements — <tr>, <td>, <th>, <tbody> — so the whole structure flattens to its text. Content inside the cells stays fully accessible; only the table semantics go.
SC 1.3.1 Info and Relationships (Level A) is the criterion on both sides. A data table whose header relationships are lost fails it; a layout table that exposes meaningless rows and columns does not strictly fail, but it adds noise that the criterion’s intent is about avoiding.
When to use role=“presentation” — and when not to
Permalink to "When to use role=“presentation” — and when not to"Use it for legacy layout tables you cannot rewrite yet: email templates rendered in a web view, CMS content from a decade ago, or a third-party widget’s markup. Adding the role is a one-attribute fix that removes table noise immediately.
Do not use it on anything that is actually tabular, however simple it looks. A two-column key–value list (“Status: Paid”, “Due: 4 March”) is a data table if the left column labels the right; if you do not want table semantics for it, use a description list (<dl>) rather than a presentational table.
The misapplication to name is adding role="presentation" to a data table to silence an axe-core or HTML validator warning about missing headers. That swaps a fixable warning for a real loss of structure. Fix the headers instead.
Also do not reach for it when a table is visually restyled for mobile. Changing display on table elements already strips semantics in some browsers — that is a bug to fix, as shown in stacked card layouts that keep table semantics, not a reason to remove them deliberately.
Annotated code example
Permalink to "Annotated code example"<!-- LEGACY LAYOUT TABLE: flattened so no table semantics are exposed -->
<table role="presentation"> <!-- ARIA: propagates to tr/td -->
<tr>
<td><img src="logo.png" alt="Northwind Traders"></td>
<!-- content inside the cell stays fully accessible -->
<td><h1>Monthly statement</h1></td>
</tr>
</table>
<!-- DATA TABLE: every signal present, nothing left to heuristics -->
<table>
<caption>Statement lines, March 2026</caption> <!-- SC 1.3.1 -->
<thead>
<tr>
<th scope="col">Date</th> <!-- SC 1.3.1 -->
<th scope="col">Description</th>
<th scope="col">Amount (EUR)</th>
</tr>
</thead>
<tbody>
<tr>
<td>2026-03-04</td>
<td>Subscription</td>
<td>49.00</td>
</tr>
</tbody>
</table>
A presentational table must not contain <th>, <caption> or scope. If it does, browsers resolve the conflict by ignoring the role — ARIA’s presentational-role conflict resolution says a focusable element or one with global ARIA attributes keeps its semantics — and you end up with a half-flattened structure. Strip them.
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Event | Data table | Layout table with role=“presentation” |
|---|---|---|
T in browse mode (NVDA/JAWS) |
Lands on the table, reads name and size | Skipped — not a table |
Ctrl+Alt+Arrow |
Moves by cell, reads headers | Not available |
| Table list | Listed under its caption | Not listed |
Linear reading (Down Arrow) |
Cell by cell with row/column context | Plain content in source order |
| VoiceOver rotor, Tables | Listed | Absent |
Integration context
Permalink to "Integration context"Layout tables mostly turn up in content rather than components: CMS rich text, imported documentation, and HTML emails displayed inside a web app. An audit rule in your pipeline can flag tables with no <th> for review; the configuration for that is in configuring axe-core rules and handling false positives.
For interactive data, the next decision after “this is a data table” is whether it should be a static table or an interactive grid, covered in choosing between grid and table roles.
Gotchas
Permalink to "Gotchas"A single-row data table. A one-row summary table (“Total: 4,210 · Paid: 3,900 · Due: 310”) is often classified as layout by the heuristic. Add a caption and <th scope="col"> cells, or express it as a <dl>.
Focusable content inside presentational cells. Links and buttons inside a role="presentation" table work normally. But if you add tabindex or aria-* to the <td> itself, that cell keeps its semantics and the flattening becomes partial.
Nested data table inside a layout table. The presentation role does not propagate into a nested <table>. A real data table inside a legacy layout wrapper stays a data table — which is what you want, but only if it has its own headers.
Testing checklist
Permalink to "Testing checklist"FAQ
Permalink to "FAQ"How can I see whether the browser treats my table as layout?
Open the accessibility tree in Chrome or Firefox DevTools and inspect the table element. A data table shows the table role with rows and cells beneath it; a layout table shows a generic or layout-table role, and its rows and cells are not exposed as table structure.
What is the difference between role="presentation" and role="none"?
None in behaviour. ARIA 1.1 introduced role=“none” as a clearer synonym because “presentation” was often misread as “this is a presentation”. Both remove the element’s implicit semantics; use whichever your codebase uses consistently.
Will role="presentation" hide the table's content from screen readers?
No. It removes the table semantics — rows, columns, headers — but the text, images, links and headings inside the cells remain fully accessible and are read in source order. To hide content entirely you would need aria-hidden, which is a different thing.
Is a key–value list a data table?
If the left column labels the right, the relationship is real and must be programmatic. A two-column table with th scope=“row” for the keys works; a description list with dt and dd is usually simpler and avoids table navigation for what is really a list.
Related
Permalink to "Related"- Converting div grids to semantic tables — the opposite migration
- Stacked card layouts — where CSS display values strip table roles
- Choosing between grid and table roles — the next decision for a data table