← All writing

· accessibility · react · design-systems

Keyboard data tables: rows, headers and cells, one Tab stop

Inside a custom table demo: roving focus, row and cell navigation, sorting, and the limits of automated accessibility checks.

A table can look complete while leaving keyboard users unable to sort a column or open an action menu. My design-system work included keyboard-first tables. This article explains the small, synthetic table in Fix 05 on the homepage, including where its custom interaction model needs more validation before reuse. It describes the public demo, not an employer’s implementation.

Choose the interaction model first

A static HTML table does not need every cell to be focusable. Screen readers already provide table navigation, and ordinary links and buttons can stay in the page’s Tab sequence. For a sortable table, buttons inside column headers are a good starting point.

A spreadsheet-like widget has different needs. The WAI-ARIA grid pattern uses one entry in the Tab sequence and author-managed navigation inside the widget. That comes with keyboard and semantic responsibilities; adding arrow-key handlers alone does not make a table an ARIA grid.

The portfolio demo explores a custom model: native table semantics with focus that can move between whole rows, headers and cells. It is not a reference implementation of the APG grid pattern or a blanket recommendation for data tables.

One Tab stop inside the table

The demo uses roving tabindex: one row, header or cell has tabindex="0"; the other navigation targets have tabindex="-1". A handled navigation key updates that state and moves DOM focus to the new target. Changing the attribute alone would not move focus.

Tab leaves the table. Returning with Shift+Tab from the next focusable control reaches the retained tab stop, provided the table has not changed. The Filter, Export and More actions controls are separate Tab stops outside the table. “One Tab stop” describes the table’s navigation targets, not the entire demo card.

Three ways to move

The public demo uses these bindings:

Keys Behavior
W / S Move between rows. From a cell, select its row first. S from a header enters the first row; W from the first row returns to a header.
A / D Enter header navigation at the current or remembered column, then move left or right.
Arrow keys Enter cell navigation at the current row or header column, then move between cells.
Enter / Space Sort when a header has focus.

Movement stops at the edges rather than wrapping. The pure moveFocus function accepts the current focus, key, dimensions and remembered column, so unit tests can cover transitions without a browser. Browser tests then check that actual focus follows the model.

For a production table, define what happens after sorting, filtering or deleting a focused row. The demo clamps a row index after filtering, but retaining an index is not the same as retaining the same record. Empty results, editable cells and virtualized rows need their own rules.

Semantics and announcements need separate checks

The demo keeps a real <table> and <th scope="col">. It puts aria-sort on the sorted header and uses a polite live region for focus descriptions such as “Row 2 of 4.” These expose state and context; they do not guarantee a particular spoken announcement. Browser, screen reader and reading mode affect what users hear, and live messages can be delayed, coalesced or duplicated by native announcements.

Native table semantics also do not automatically switch a screen reader into an interaction mode that passes arrow keys or letters to JavaScript. Verify the chosen model with the browser and assistive technology combinations your users rely on. For conventional sortable tables, the APG sortable table example is a better starting point than copying this demo’s focusable headers.

Single-letter shortcuts need boundaries

WCAG 2.1.4 requires character-only shortcuts to be switchable, remappable, or active only when the relevant component has focus. The demo attaches W/A/S/D handling to the table and ignores Alt, Control and Meta combinations. A visible legend explains the bindings.

That focus boundary matters, but it is not proof of overall accessibility. If you add text fields or editable cells, exclude their editing events, including composition input, from navigation handling. Check speech input and screen-reader key conflicts, and consider an off switch for optional shortcuts.

Actions must work without a mouse

Filter and More actions use a menu-button interaction: Enter, Space or ArrowDown opens the menu, arrow keys move between items, and Escape closes it and returns focus. Export is a plain button that downloads the visible rows as CSV. The APG menu button pattern provides the baseline behavior.

What the tests establish

The repository tests focus transitions, sorting, filtering, menus and CSV output. Playwright exercises keyboard paths and checks that the deliberately broken table’s controls cannot be reached. Article pages are checked with axe in light and dark themes, alongside metadata, links and small-screen layout checks.

These checks catch regressions; they do not establish that every screen-reader interaction is usable. Manual checks should cover entering and leaving the table, identifying the active row and column, hearing sort changes, and completing the task without relying on the visual focus indicator. That assistive-technology review remains necessary before treating this custom model as production guidance.

Related case study: read the full story →