Prepared Docs

Prepared Docs

⌘K

    Loading navigation…

User

  1. Blocks
  2. Data Table
  3. Accessibility

Data table

Accessibility

Overview

DataTable renders a real <table> with <thead>, <tbody>, and <th>/<td> so browsers and assistive tech expose proper row and column context. The component owns core grid semantics, keyboard paths through headers and row controls, and the plumbing for sort, selection, and pagination. Your app is responsible for column labels, bulk-action copy, empty-state strings, focus handling across async refreshes, and any filter chrome you place above the grid.

What the component handles

  • Native <table> structure with <thead>, <tbody>, <tr>, <th>, and <td>; row and column context announced without additional ARIA.
  • aria-sort on sortable headers, with state values ascending, descending, and none reflecting the active sort.
  • aria-expanded on the row disclosure button when expandableChild is provided; expanded content participates in tab order.
  • aria-checked on selection checkboxes, including mixed state for the select-all when some rows are selected.
  • aria-busy="true" on the table root while loading is true, suppressing interaction during initial load.
  • Keyboard reachability for every built-in control—sort, select, expand, row action menus, pagination buttons.
  • Pagination clamps an out-of-range page onto the last known page so Previous/Next stay usable after filters shrink the result set. Unknown page counts are not clamped.

What you must provide

  • Column header text that is short, unique, and describes the data; avoid icon-only headers without an accessible name.
  • Accessible names for icon-only row actions (menu trigger, expand) via labelled buttons or tooltip-bound titles.
  • Copy for empty, loading, and error states that reads clearly on its own.
  • Correct focus management after async refreshes—preserve the focused row when filters apply, or send focus to a clear anchor (the first row, the filter bar) when a refresh replaces the dataset.
  • Bulk action labels in the floating toolbar that pluralize ("Delete 3 incidents") via formatMessage.
  • Confirm dialogs or inline warnings for destructive bulk actions; the toolbar alone isn't a permission to silently delete.

Keyboard behavior

  • Tab / Shift+Tab: move through headers, filter controls, rows, row-level actions, pagination, and the floating toolbar in reading order.
  • Enter or Space: activate the focused control: cycle sort, toggle checkbox, open the row action menu, toggle row expansion.
  • Arrow keys: navigate between cells when focus is inside the grid; navigate menu items when the row action menu is open.
  • Escape: close an open row menu or clear the floating toolbar when it is dismissible.
  • Shift+Click: range selection for checkboxes (keyboard range via Shift+Space across focused rows where the app provides the handler).

Semantics and roles

  • Use real <th scope="col"> for headers and pair with aria-sort. Don't override the default table role.
  • When a column displays a single icon header (e.g. status swatch), provide an invisible label via visuallyHidden text so screen readers hear "Status column".
  • Row action menus use Menu from @prepared911/ui-core—already menu/menuitem. Items should have readable names, not solely icons.
  • Selection checkboxes must have accessible names; the component wires "Select row" / "Select all rows" by default, but customize when the row identity is meaningful ("Select incident 12345").
  • Pagination controls render as <button> elements with visible or labelled names ("Next page", "Previous page"); disabled state is announced.

Focus management

  • After sort or filter: keep focus on the header or filter control the user activated. Don't jump focus into the body; it disorients screen reader users.
  • After row action: return focus to the trigger (the action menu button, the checkbox). After row removal, send focus to the next row in the same column, not the first.
  • After expand: keep focus on the expand button; inside the expanded region, the first focusable control picks up focus on Tab.
  • After pagination: return focus to the first cell of the first row in the new page, or to the pagination control if the user stayed on it. Don't send focus to the top of the page.

Screen reader announcements

  • Sort changes: announce "Sorted by Name ascending" via a polite live region so users who trigger sort with the keyboard know the grid reordered. The component ships aria-sort; the live-region narration is up to the app when sort is meaningful mid-task.
  • Filter applied: announce "Showing 24 of 312 incidents" after a filter runs; pair with an idle delay so repeated typing doesn't spam the user.
  • Selection count: announce the floating toolbar count on change ("3 incidents selected"). The toolbar also shows the count visually.
  • Loading: when loading is true, the grid is aria-busy. For partial refreshes, fall back to a short polite announcement ("Loading next page").
  • Empty and error: render an accessible region inside the body with a role="status" or equivalent so the new state is announced on first paint.

Reduced motion

  • DataTable keeps motion minimal: row expand animates a short height transition, and the floating toolbar slides up when selection opens. Both respect prefers-reduced-motion: reduce; if you customize animations around the grid, gate them on the media query too.

Touch targets and responsive behavior

  • Checkboxes, menu triggers, and pagination controls meet the 44×44 target when rendered inside the default row height. If you override row height, verify that touch targets still hit the threshold.
  • Column visibility controls are the accessible escape hatch on narrow desktops; don't hide columns automatically without offering the user a way to reveal them.
  • Horizontal scroll is keyboard-reachable via arrow keys when focus is in the table; keep this working when you wrap the grid in a drawer or other scroll container.

Known caveats

  • Paging across a selection is ambiguous by default. The component preserves selection across pages when you keep the same underlying data source; spell this out in the toolbar ("3 selected across all pages") so users aren't surprised.
  • DataTable does not announce sort changes by itself—add the polite live region in your app when sort is part of a mid-task flow.
  • expandableChild is keyboard-reachable, but the first focusable element inside depends on your render. Put a focusable anchor near the top of each expanded region so Tab doesn't skip to the next row.
  • When a bulk action removes rows, the floating toolbar dismisses; restore focus to a sensible anchor (the filter bar, the first row) rather than letting focus fall off the document.
  • Screen reader pagination announcements can feel verbose with long tables. Prefer meaningful page labels ("Incidents 51–75 of 240") over bare page counts when the data is long-lived.

Previous

Data Table / API and Development

Next

Filters / Sort Models and Hooks

On this page

Overview
What the component handles
What you must provide
Keyboard behavior
Semantics and roles
Focus management
Screen reader announcements
Reduced motion
Touch targets and responsive behavior
Known caveats