Stockio

Accessible Tables: Headers, Captions and Responsive Layouts

Build data tables that retain meaningful headers, readable captions and usable controls on small screens, with practical markup and a focused accessibility test plan.

By Noor Haddad

Accessibility Specialist ·

On this page

A data table is useful because readers can compare values across two dimensions. Accessibility work should preserve that relationship, not simply make the component fit a narrow screen. A visually tidy collection of cells can still be incomprehensible when someone navigates by row, enlarges text, or listens to one value at a time.

Start with the relationships people need

Write down what each row represents, what each column measures, and which information identifies a record. For an invoice table, that might be invoice number, customer, due date, and amount. A user hearing “£240” needs enough context to know whose invoice it describes and what the amount means.

Use the contrast checker to compare foreground and background values for header text, body text, and interactive controls. Check selected and hover states separately. Readable cells are necessary, but contrast cannot establish the relationships between them; those come from the markup.

Choose a table for genuinely tabular information, not for arranging a form or aligning a page. The WAI tables tutorial offers examples of simple and complex relationships. Start with the simplest structure that faithfully represents the data, and split unrelated datasets rather than forcing everything into one enormous matrix.

Give the table a name and real headers

Use native table elements before considering ARIA. A caption identifies the dataset for people scanning visually and for users locating tables through assistive technology. Make it specific: “Outstanding invoices, April 2026” is more useful than “Results.” Put lengthy explanations in adjacent text rather than stuffing every instruction into the caption.

For straightforward tables, use column header cells with scope set to column and row header cells with scope set to row. A row header should identify the record, not merely be whichever cell happens to appear first. The browser can then expose relationships without custom keyboard behavior.

The following markup keeps an invoice identifier associated with its amount and due date:

<table>
<caption>Outstanding invoices, April 2026</caption>
<thead>
  <tr>
    <th scope="col">Invoice</th>
    <th scope="col">Due date</th>
    <th scope="col">Amount</th>
  </tr>
</thead>
<tbody>
  <tr>
    <th scope="row">INV-1042</th>
    <td>30 April 2026</td>
    <td>£240.00</td>
  </tr>
</tbody>
</table>

Avoid replacing these elements with generic containers and a large collection of roles. Native semantics already encode the structure. Consult the MDN table reference when deciding where captions, header groups, and body groups belong. Empty header cells should not stand in for missing labels; give action columns a meaningful heading such as “Invoice actions.”

Handle complex headings and table controls deliberately

Grouped columns can need more than a single header per value. First ask whether repeated, explicit headings would make the table easier to understand. Simpler structure benefits everyone, especially when the alternative is several levels of merged cells with abbreviated labels.

Where complex relationships are unavoidable, use unique header IDs and explicit headers attributes on relevant cells. Each data cell should reference every applicable heading. Verify the resulting associations in actual screen readers rather than assuming a visually correct span creates the intended spoken context. Avoid duplicate IDs when multiple table instances appear on a page.

Sorting belongs on a real button inside the relevant header. Expose the active direction through aria-sort on the sorted header cell, and update it when the order changes. Keep focus on the button after sorting. A small arrow alone does not explain either the action or the current direction.

For checkboxes, provide row-specific names such as “Select invoice INV-1042.” For repeated action links, include enough accessible context to distinguish “View invoice INV-1042” from the next row's link. Do not make every cell focusable: ordinary tables are read with table navigation commands, while Tab should reach genuine controls. Our WCAG interface guide provides broader context for names, roles, and keyboard access.

Preserve relationships on narrow screens

A scrolling wrapper is often the least surprising responsive treatment for comparison-heavy data. It lets columns retain their meaning without shrinking text to illegibility. Keep horizontal overflow inside the component so unrelated page content still reflows normally. Data tables can qualify for WCAG's two-dimensional layout exception, but that is not permission to make the entire page scroll sideways.

<p id="invoice-scroll-hint">Scroll horizontally to see all columns.</p>
<div class="table-scroll" role="region"
   aria-label="Outstanding invoices"
   aria-describedby="invoice-scroll-hint" tabindex="0">
<!-- Insert the complete native table here. -->
</div>
.table-scroll {
max-width: 100%;
overflow-x: auto;
}
.table-scroll:focus-visible {
outline: 3px solid #175cd3;
outline-offset: 3px;
}
.table-scroll table {
min-width: 38rem;
border-collapse: collapse;
}

Make a wrapper focusable when keyboard users need it to operate overflow, and check browser behavior rather than creating redundant stops everywhere. Give a focusable region a useful name and a visible focus indicator. The hint must match reality; conditionally showing it only when content overflows can reduce noise.

A stacked-card alternative can work for record browsing, but it weakens cross-row comparison. Do not rely solely on CSS-generated labels or assume changing table elements to block layout preserves semantics across all browser combinations. Test your actual implementation. The responsive container guide helps separate page-level sizing decisions from the table's necessary overflow.

Test with realistic content and navigation

Use long customer names, missing values, negative amounts, and translated headings. A placeholder dash needs an understandable meaning, such as “Not available,” not an unexplained visual convention. Check text enlargement and a narrow viewport, including whether sticky headers or columns conceal focused controls.

Run a keyboard pass first: enter the scrolling region, move it horizontally where needed, activate sorting, and reach every row action without getting trapped. Then use a screen reader's table commands to move across a row and down a column. Confirm that the caption, row identifier, and applicable column heading provide useful context.

Check a representative desktop and mobile assistive-technology combination when the table is important to core tasks. Automated audits can identify some structural omissions, but they cannot decide whether a caption explains the dataset or whether a header association makes business sense. The WCAG information and relationships guidance explains the underlying requirement.

Finally, test a refresh, sorting update, and empty result. Stable focus and a clear empty-state explanation matter just as much as the populated table. Document the expected header announcements so future refactors have a concrete acceptance target.

Key takeaways

  • Preserve native table structure, with a useful caption and explicit headers.
  • Keep sorting and row actions keyboard accessible and clearly named.
  • Prefer contained overflow when comparison requires two dimensions.
  • Verify header announcements, zoom behavior, and focus with realistic data.
Featured sponsor

Reach designers & developers at work

Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.

Become a sponsor

Keep reading

Related articles

More in Accessibility
Accessibility

10 Best Accessibility Courses and Learning Resources

Learn practical web accessibility through standards, tutorials, videos, and real interface examples. These ten trusted resources help beginners and working teams build skills and choose a useful next step.

Includes: Contrast checkerNoor HaddadNoor Haddad
Accessibility

10 Best Accessibility Browser Extensions for Testing

Compare ten browser add-ons for spotting accessibility issues while you browse or build, from automated page scans to heading and contrast checks, plus advice for turning findings into fixes.

Includes: Contrast checkerTheo OkaforTheo Okafor
Accessibility

12 Best Accessibility Testing Tools for Websites

Compare twelve practical accessibility testing tools, from browser extensions and automated checks to screen readers and code libraries, and learn how to choose a sensible mix for your site.

Includes: Contrast checkerNoor HaddadNoor Haddad
Accessibility

Accessible Tabs: Build a Keyboard-Friendly Tabs Component

Implement accessible tabs with correct roles, roving tabindex, manual activation, and panel relationships, then test keyboard behavior and progressive enhancement.

Includes: Contrast checkerTheo OkaforTheo Okafor
Accessibility

Automated Accessibility Testing With axe and Lighthouse

Combine axe state-based checks with Lighthouse page audits, triage actionable findings, and add manual verification without mistaking a clean report for WCAG conformance.

Includes: Contrast checkerNoor HaddadNoor Haddad
Accessibility

How to Do an Accessibility Audit and Write the Report

Scope an accessibility audit, combine automated and manual testing, and write evidence-based findings that developers can reproduce, prioritize, fix, and retest.

Includes: Contrast checkerTheo OkaforTheo Okafor