Accessible Data Tables
debt(d3/e2/b3/t5)
Closest to 'default linter catches the common case' (d3). Tools like axe, Lighthouse, and WAVE (all listed in detection_hints.tools) are widely used and often integrated into CI pipelines or browser devtools. They automatically flag missing <caption>, <th> without scope, and layout tables. These aren't quite compiler errors (d1), but they are standard automated checks that most accessibility-aware teams run by default.
Closest to 'one-line patch or single-call swap' (e1), +1 to e2. The quick_fix is straightforward: replace <td> with <th scope='col'> or <th scope='row'> and add a <caption>. This is mostly a find-and-replace within a single template file. It's slightly more than a single-line fix since multiple elements need updating across the table, but it stays within one file and is a simple mechanical change.
Closest to 'localised tax' (b3). Accessible table markup applies only to web contexts and specifically to components that render data tables. Once the pattern is established (use semantic <table>, <th>, scope, <caption>), it's a localized convention that doesn't impose weight on the rest of the codebase. It does require ongoing discipline in every table component, but it doesn't shape architectural decisions beyond the table markup itself.
Closest to 'notable trap' (t5). The misconception is well-documented: developers believe adding role='table' to a <div> makes it equally accessible, when in fact native <table> elements provide rich built-in semantics and browser navigation APIs that ARIA roles only partially replicate. Additionally, common mistakes like using <td> instead of <th>, omitting scope attributes, and using tables for layout are traps that competent developers who haven't focused on accessibility will reliably fall into. These contradict the intuition that 'it looks right visually so it must be fine.'
Also Known As
TL;DR
Explanation
Screen readers navigate tables by reading each cell along with its associated headers. Without proper markup, a user hears '42' with no context; with correct markup they hear 'Q3, Revenue, 42'. Key elements: `<table>` for the container, `<caption>` for the table title (announced first), `<thead>` / `<tbody>` / `<tfoot>` for structural sections, `<th>` with `scope='col'` or `scope='row'` for header cells, and `<td>` for data cells. For complex tables with merged cells, `headers` and `id` attributes explicitly associate cells with their headers. The `summary` attribute is obsolete — use `<caption>` or `aria-describedby` instead. Avoid using tables for layout; use CSS Grid or Flexbox. When a table has no natural caption, add `aria-label` or `aria-labelledby` on the `<table>` element.
Common Misconception
Why It Matters
Common Mistakes
- Using `<td>` for header cells instead of `<th>` — screen readers do not know to announce the cell as a header.
- Missing `scope` attribute on `<th>` elements in tables with both row and column headers — causes ambiguous header associations.
- Using `<table>` for page layout — confuses screen reader users who navigate table by table and announce row/column counts.
- Omitting `<caption>` — users cannot identify the table's purpose before entering it.
- Using the obsolete `summary` attribute instead of `<caption>` or `aria-describedby`.
Code Examples
<!-- No headers, no caption, uses td for headers -->
<table>
<tr>
<td>Product</td>
<td>Q1</td>
<td>Q2</td>
</tr>
<tr>
<td>Widgets</td>
<td>120</td>
<td>145</td>
</tr>
</table>
<!-- Fully accessible table -->
<table>
<caption>Quarterly Sales by Product (units)</caption>
<thead>
<tr>
<th scope="col">Product</th>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Widgets</th>
<td>120</td>
<td>145</td>
</tr>
<tr>
<th scope="row">Gadgets</th>
<td>98</td>
<td>110</td>
</tr>
</tbody>
</table>