Accessibility
react-data-table-component documents the ARIA semantics, keyboard interactions, and known considerations for each interactive region.
Labelling the table
Always provide ariaLabel so screen readers can identify DataTable. Without it, assistive technology announces a generic “table” with no context.
<DataTable
ariaLabel="Employee directory"
columns={columns}
data={data}
/>
Table structure
react-data-table-component renders div elements with explicit ARIA roles, giving screen readers a full table structure.
| Element | Role / attribute |
|---|---|
| Table wrapper | role="table" (or role="grid" with cellNavigation — see below), aria-label (from ariaLabel prop), aria-busy during load |
| Header section | role="rowgroup" |
| Body section | role="rowgroup" |
| Header row | role="row" |
| Data row | role="row", aria-selected (when selectableRows is enabled) |
| Data cell | role="cell" (or role="gridcell" with cellNavigation) |
| Column header | role="columnheader", aria-sort (sortable columns). Contains the sort control, filter button and column menu button |
| Sort control | role="button" inside the column header, focusable only when the column is sortable |
| Expander and hidden select-all header slots | role="cell", so header and body rows have the same number of cells |
With columnGroups, the column headers sit in a single role="row"; the group labels above them are visual only.
By default this is a static table: nothing but sortable headers is focusable, which is the right shape for a screen reader’s native table-reading commands. Passing cellNavigation turns it into an interactive grid (WAI-ARIA grid pattern) instead — every cell becomes focusable via a single roving tab stop, and arrow keys move between them. See Keyboard navigation for the full key reference and the reasoning for making this opt-in rather than the default.
Sorting
Sortable column headers expose aria-sort on the role="columnheader" element so screen readers announce the current direction.
| State | aria-sort value |
|---|---|
| Not sorted | "none" |
| Sorted A → Z / low → high | "ascending" |
| Sorted Z → A / high → low | "descending" |
| Column not sortable | attribute omitted |
Keyboard: the sort control inside a sortable header receives tabIndex={0}. Press Enter or Space to toggle the sort direction. Non-sortable headers are removed from the tab order. Keyboard focus draws a 2px :focus-visible ring in the theme’s primary color (--rdt-color-primary) around the header cell. The filter, menu, expander and pagination buttons get the same ring. With cellNavigation enabled, header cells instead participate in the grid’s roving tabindex: arrowing onto a sortable header focuses its sort control, and a non-sortable header takes focus itself. See Keyboard navigation.
Row selection
When selectableRows is enabled:
- Each data row carries
aria-selected={true|false}so screen readers announce selection state. - The select-all checkbox in the header has
aria-label="Select all rows". - Per-row checkboxes have
aria-label="Select row {id}"where{id}is the row’s key field value. - The indeterminate state (some-but-not-all rows selected) is set via the native
indeterminateDOM property, which screen readers announce correctly. - With
cellNavigationenabled, checkboxes are reachable by arrowing to their column and are toggled with Space. See Keyboard navigation.
Column filters
Each filterable column header contains a filter toggle button, inside the role="columnheader" element. The popup is a role="dialog".
Filter toggle button
| Attribute | Value |
|---|---|
aria-label |
"Filter active: {name}" when a filter is applied, "Filter column: {name}" otherwise. {name} is the column name when it is a string; otherwise the label has no suffix |
aria-haspopup |
"dialog" |
aria-expanded |
true while the popup is open, false when closed |
Filter panel (popup)
| Attribute | Value |
|---|---|
role |
"dialog" |
aria-label |
"Column filter" |
Focus moves automatically to the first focusable element (the operator <select>) when the panel opens.
Keyboard interactions inside the panel:
| Key | Action |
|---|---|
| Escape | Close the panel |
| Tab / Shift+Tab | Move between operator select, value inputs, and action buttons |
Controls inside the panel
| Control | aria-label / attribute |
|---|---|
Operator <select> |
aria-label="Filter operator" |
Primary value <input> |
aria-label="Filter value" |
Secondary value <input> (Between) |
aria-label="Filter second value" |
| AND toggle button | aria-pressed reflects active state |
| OR toggle button | aria-pressed reflects active state |
| Add condition button | aria-label="Add a second filter condition" |
| Remove condition button | aria-label="Remove condition" |
Inline editing
Editor inputs, selects and checkboxes are labelled by their column header through aria-labelledby, so the label matches whatever column.name renders, including React nodes. When the header is not rendered (noTableHead), a string column.name is used as the aria-label instead.
Expandable rows
The expand toggle is a <button> with aria-label="Expand Row" or "Collapse Row". Expanded content renders inline beneath the row and is read naturally by screen readers.
Keyboard: press Enter or Space on the expander button to toggle. If expandOnRowClicked is set, pressing Enter on the row itself also toggles. With cellNavigation enabled, the expander button is reachable by arrowing to its column. See Keyboard navigation.
Pagination
The pagination controls are wrapped in a <nav aria-label="Table pagination">, distinguishing them from other landmarks on the page.
| Button | aria-label |
|---|---|
| First page | "First Page" |
| Previous page | "Previous Page" |
| Next page | "Next Page" |
| Last page | "Last Page" |
Disabled buttons have both disabled and aria-disabled="true". The rows-per-page <select> uses the rowsPerPageText option value as its aria-label.
Loading and empty states
- While data is loading, the table wrapper carries
aria-busy="true". Skeleton rows arearia-hidden="true"so they are not read aloud. - When a re-fetch overlays existing rows, the overlay is
aria-hidden="true"andaria-busyon the wrapper communicates the busy state. - When there is no data, the message renders in a single-cell row and is wrapped in
role="status"so screen readers announce it when it appears.
Resize handles
Column resize handles are aria-hidden="true". They are drag-only with no keyboard equivalent.
Tips for consumers
- Always provide
ariaLabel. Without it, screen readers announce a generic “table”. - Always set
idon filterable columns. The filter state is keyed bycolumn.id; omitting it silently disables filtering. - Use descriptive
namevalues. Columnnamelabels the header, the sort control, the filter button and inline editors. - Avoid icon-only column names without labels. If
column.nameis a React node (e.g. an icon), wrap it with an accessible label (aria-labelor a visually-hidden<span>). - Test with a keyboard. Tab through the header row, sort with Enter, open a filter panel with Enter or Space, navigate inputs with Tab, and close with Escape.