Accessible Tables

Data tables are one of the most misused HTML elements on government websites. A table that looks fine visually can be completely unusable for screen reader users and cognitively overwhelming for neurodivergent users. This guide shows the markup patterns and design decisions that make tables work for everyone.

Note: Future updates to this page will add interactive working components — live examples, editors, and tools — directly in the browser.

Why this matters for neurodivergent users

Dense tables with no visual hierarchy are a significant barrier for ADHD and dyslexia users. Alternating row colors, clear column labels, and generous white space help the eye track across rows. Keeping tables narrow (fewer than 6–8 columns) and breaking large datasets into multiple focused tables reduces cognitive load.

For screen reader users, proper scope attributes and a descriptive <caption> turn a table from a wall of disconnected data into a navigable structure — every cell announces its row and column header.

Required WCAG success criteria

  • SC 1.3.1 Info and Relationships (A) — table headers must be marked up with <th> and scope, not just styled differently.
  • SC 1.3.2 Meaningful Sequence (A) — reading order must be logical when CSS is removed.
  • SC 4.1.2 Name, Role, Value (A) — the table must have a programmatic label (<caption> or aria-label).
  • SC 1.4.3 Contrast Minimum (AA) — cell text and header text must meet 4.5:1 (normal) or 3:1 (large/bold ≥ 18.67px).

Example: accessible audit results table

This is the pattern used throughout this site. Every table element is shown with its required attribute:

WCAG table attributes. Each row describes one required HTML element and its purpose.
ElementRequired attributePurposeWCAG SC
<table>Container — do not use for layout1.3.1
<caption>Visible title announced before table content4.1.2
<thead>Groups column header rows1.3.1
<th scope="col">scope="col"Column header — screen reader prefixes each cell with this1.3.1
<th scope="row">scope="row"Row header — identifies what the row is about1.3.1
<tbody>Groups data rows, enables browser optimization1.3.1

The minimal accessible table pattern

<div style="overflow-x: auto">
  <table>
    <caption>
      Security audit grades. A = no findings. F = critical unresolved.
    </caption>
    <thead>
      <tr>
        <th scope="col">Category</th>
        <th scope="col">Date</th>
        <th scope="col">Grade</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <th scope="row">Code health (Fallow)</th>
        <td>2026-05-23</td>
        <td>B (80)</td>
      </tr>
      <tr>
        <th scope="row">Dependency scan (npm audit)</th>
        <td>2026-05-23</td>
        <td>B</td>
      </tr>
    </tbody>
  </table>
</div>

Complex tables: headers with id + headers

When a table has cells that span rows or columns, scope is not enough. Use id on every header and headers on every data cell listing all header IDs that apply:

<table>
  <caption>Budget by department and quarter</caption>
  <thead>
    <tr>
      <td></td>
      <th id="q1" scope="col">Q1</th>
      <th id="q2" scope="col">Q2</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th id="eng" scope="row">Engineering</th>
      <td headers="eng q1">$120k</td>
      <td headers="eng q2">$115k</td>
    </tr>
  </tbody>
</table>

Responsive tables

Wrap every table in a div with overflow-x: auto so it scrolls horizontally on small screens instead of breaking the layout. This satisfies SC 1.4.10 Reflow (AA).

For very wide tables, consider a card pattern on mobile: each row becomes a card with the column heading repeated as a label using CSS content and data-label attributes.

Design for neurodivergent users

  • Alternate row background — a very subtle tint (not high-contrast) on even rows helps track across wide tables without creating a contrast violation.
  • Left-align text columns, right-align numbers — number columns should be right-aligned and use a monospace font so digits align vertically.
  • Keep tables narrow — prefer 4–6 columns. Split wide tables into two focused tables.
  • Use sorting where possible — sortable columns reduce the cognitive cost of finding information, but sorting controls must meet SC 4.1.2 (announce sort state via aria-sort).
  • Plain language column headings — avoid abbreviations in headers. The caption or a <details> glossary can define any abbreviations used inside cells.

Testing checklist

  • Navigate with NVDA or VoiceOver in table browse mode — every cell should announce its row and column header(s).
  • Zoom to 400% — confirm layout does not break (SC 1.4.4, 1.4.10).
  • Run axe-core — rule td-headers-attr, th-has-data-cells, scope-attr-valid.
  • Verify color contrast on header backgrounds (th[scope="col"] and th[scope="row"]) with the foreground text color at both 4.5:1 (AA) and 7:1 (AAA).