Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

A summary of notable changes per release. For the full commit history see the [repository on GitHub](https://github.com/jbetancur/react-data-table-component/commits/master).

## 8.11.0

### Behavior changes

These are accessibility fixes, but they change the rendered markup, so tests that query the header by role may need updating.

- `role="columnheader"` and `aria-sort` now sit on the outer header cell (`.rdt_TableCol`) for every column. The inner `.rdt_columnSortable` element is now a `role="button"` sort control, and is only focusable when the column is sortable. Tests or styles that selected the inner element by `[role="columnheader"]` should use `.rdt_TableCol` or `[data-sort-handle]` instead.
- Clicking the `columnheader` element in a test no longer sorts, because the click handler is on the sort control inside it. Replace `fireEvent.click(getByRole('columnheader', { name: 'Name' }))` with `fireEvent.click(getByRole('button', { name: 'Name' }))`. Real mouse clicks are unaffected.
- A column header's accessible name now includes the buttons inside it, so an exact-match `getByRole('columnheader', { name: 'Name' })` no longer matches on filterable or menu columns. Query the sort control with `getByRole('button', { name: 'Name' })`, or match the header with a regex.
- The filter button's label now includes the column name ("Filter column: Name"), so screen reader users can tell several filter buttons apart. The column name is appended to a custom `filterColumnAriaLabel` / `filterActiveAriaLabel` as well. → [Filtering](/docs/filtering)

### Bug fixes

- The column filter and header menu buttons no longer sit outside their column header. They were owned directly by the header row, which failed axe `aria-required-children` and left screen readers with no column context for them. ([#1398](https://github.com/jbetancur/react-data-table-component/issues/1398))
- The row expander cell now has `role="cell"`, so the expand button is no longer a direct child of the row. The expander and hidden select-all header slots are now cells too, so header and body cell counts line up.
- Grouped headers (`columnGroups`) now render their column headers inside a `role="row"`. They previously had no row parent at all.
- The "no data" message now sits in a row and cell instead of directly inside the table, which only allows rows.
- Inline editor inputs, selects and checkboxes are now labelled by their column header, including when `column.name` is a React node. They previously had no accessible name. → [Inline editing](/docs/inline-editing)
- Keyboard focus is now clearly visible on the sort control, filter button, column and row menu buttons, expander button and pagination buttons. They removed the browser outline and showed focus only as a faint gray tint (or, for the sort control, by fading), which did not meet 3:1 contrast. They now get a 2px ring in the theme's primary color, shown for keyboard focus only. → [Accessibility](/docs/accessibility)
- Horizontal scrolling over the table now scrolls the page or container around it once the table has no more to scroll. The responsive wrapper set `overscroll-behavior-x: contain`, which swallowed side-scroll and swipe gestures, most noticeably on mobile and trackpads. ([#1392](https://github.com/jbetancur/react-data-table-component/issues/1392))

---

## 8.10.0

### New features
Expand Down
755 changes: 387 additions & 368 deletions apps/docs/package-lock.json

Large diffs are not rendered by default.

27 changes: 19 additions & 8 deletions apps/docs/src/pages/docs/accessibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,19 @@ Always provide `ariaLabel` so screen readers can identify `DataTable`. Without i
| 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"` |
| 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](/docs/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` so screen readers announce the current direction.
Sortable column headers expose `aria-sort` on the `role="columnheader"` element so screen readers announce the current direction.

| State | `aria-sort` value |
| --- | --- |
Expand All @@ -54,7 +58,7 @@ Sortable column headers expose `aria-sort` so screen readers announce the curren
| Sorted Z → A / high → low | `"descending"` |
| Column not sortable | attribute omitted |

**Keyboard**: sortable headers receive `tabIndex={0}`. Press **Enter** or **Space** to toggle the sort direction. Non-sortable headers are removed from the tab order. Keyboard focus is indicated with a visible `:focus-visible` outline in the theme's primary color. With `cellNavigation` enabled, header cells instead participate in the grid's roving tabindex — see [Keyboard navigation](/docs/keyboard-navigation).
**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](/docs/keyboard-navigation).

---

Expand All @@ -72,14 +76,15 @@ When `selectableRows` is enabled:

## Column filters

Each filterable column header contains a filter toggle button. The popup is a `role="dialog"`.
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"` when a filter is applied, `"Filter column"` otherwise |
| `aria-pressed` | `true` while the popup is open, `false` when closed |
| `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)

Expand Down Expand Up @@ -111,6 +116,12 @@ Focus moves automatically to the first focusable element (the operator `<select>

---

## 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.
Expand Down Expand Up @@ -138,7 +149,7 @@ Disabled buttons have both `disabled` and `aria-disabled="true"`. The rows-per-p

- While data is loading, the table wrapper carries `aria-busy="true"`. Skeleton rows are `aria-hidden="true"` so they are not read aloud.
- When a re-fetch overlays existing rows, the overlay is `aria-hidden="true"` and `aria-busy` on the wrapper communicates the busy state.
- When there is no data, the empty-state container has `role="status"` so screen readers announce the "no records" message when it appears.
- 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.

---

Expand All @@ -152,6 +163,6 @@ Column resize handles are `aria-hidden="true"`. They are drag-only with no keybo

- **Always provide `ariaLabel`.** Without it, screen readers announce a generic "table".
- **Always set `id` on filterable columns.** The filter state is keyed by `column.id`; omitting it silently disables filtering.
- **Use descriptive `name` values.** Column `name` is the visible label announced for sortable headers and filter dialogs.
- **Use descriptive `name` values.** Column `name` labels the header, the sort control, the filter button and inline editors.
- **Avoid icon-only column names without labels.** If `column.name` is a React node (e.g. an icon), wrap it with an accessible label (`aria-label` or 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.
6 changes: 3 additions & 3 deletions apps/docs/src/pages/docs/localization.astro
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ const custom: Localization = {
};

filter?: {
filterColumnAriaLabel?: string; // default: 'Filter column'
filterActiveAriaLabel?: string; // default: 'Filter active'
filterPanelAriaLabel?: string; // default: 'Filter panel'
filterColumnAriaLabel?: string; // default: 'Filter column' (': {column name}' is appended)
filterActiveAriaLabel?: string; // default: 'Filter active' (': {column name}' is appended)
filterPanelAriaLabel?: string; // default: 'Column filter'
operatorAriaLabel?: string; // default: 'Filter operator'
valuePlaceholder?: string; // default: 'Value'
valueAriaLabel?: string; // default: 'Filter value'
Expand Down
Loading
Loading