X-03 · Composites · App components
All elements · DataGrid on the Composites page
Last checked 2026-10-22 · Awaiting approval · Accessibility tests
Approve DataGrid
Approving as
Table plus column persistence, a column picker and infinite load. Adds a toolbar, a sticky-header scroll body, the picker popover and the load-more sentinel.
The specimen below is copied from
datagrid.html, which shows it inside
the app shell, by bun scripts/design-pages/build-composite-pages.mjs — edit that file, not
this one; --check fails when they drift.
| Axis | Values |
|---|---|
.qf-grid · __body · __sentinel ·
__drop-indicator
|
regions and reorder feedback |
.qf-column-picker |
List of toggles with drag handles |
| Approve invoice | INV-2026-0412 | jdJane Doe | Tomorrow | High |
| Index batch | Batch #2041 | akAlex Kim | Today | Medium |
| Review contract | MSA-2026 | mrMaya Rossi | 3 days | Low |
| Assess claim | CS-2041 | Unassigned | Next week | High |
| Archive statements | Finance / 2025 | tsTom Sato | Next month | Low |
| Approve invoice | INV-2026-0398 | jdJane Doe | Next month | Medium |
| Verify identity | Person 10442 | akAlex Kim | Next month | Low |
Guidance
When to use
- Use it for a large, scrolling record set, such as a task queue of a thousand rows, where users scan and compare columns.
- Use it when users choose, reorder and resize columns and expect those choices to persist.
- Use it when rows load in pages as the user scrolls rather than through page buttons.
- Use it when the column headers must stay visible while the body scrolls.
When to consider something else
- Use a plain Table for a short, fixed data set that needs no column picker or infinite load. See Table.
- Use Search with the table view when users also need a query, filters and a view switch. See Search.
- Use a List when each record is a single line of text with an action, not a set of columns. See List.
- Use Pagination instead of the load-more sentinel when users need to return to a known page. See Pagination.
Usability
-
Show both the total and the loaded count in the toolbar, for example
1,204 rows · 40 loaded. - Keep the identifying column (Task in the reference) checked and disabled in the column picker so it cannot be hidden.
- Give the column picker a Reset action that restores the default columns.
-
Show
.qf-grid__sentinelwith visible text (Loading more…) while the next rows load. - Right-align numeric columns and use
aria-sorton the sorted header.
- Do not use infinite load when users must reach the footer or content below the grid.
- Do not make drag the only way to reorder or resize columns.
- Do not hide the column that names the row; every row needs a readable identifier.
- Do not show status only by chip color; keep the text.
Accessibility
Keyboard
| Keys | Action |
|---|---|
| Tab | Moves through the toolbar buttons, then the sortable header buttons, then focusable controls in the rows. |
| Enter or Space |
On a header's .qf-table__sort button, sorts by that column or reverses the order.
|
| Enter or Space | On Columns, opens the column picker; Escape closes it and returns focus to the button. |
| Space | In the column picker, shows or hides the focused column. |
| Arrow keys | If the binding adds cell navigation, it must follow the ARIA grid pattern: arrows move between cells, Home and End to row ends. |
Roles and names
-
The grid is a native
<table class="qf-table">with<th scope="col">headers inside.qf-grid__body; it stays a table unless the binding implements the fullrole="grid"pattern. -
The sorted header carries
aria-sort="ascending"or"descending"; other sortable headers omit it. -
The toolbar is
role="toolbar"; the Columns button carriesaria-expandedandaria-controls, and the binding should addaria-haspopup="dialog". -
The column picker is a popover holding a
.qf-listof labeled checkboxes; drag handles are decorative andaria-hidden. -
Resize handles (
.qf-table__resize) are not focusable in the reference; the binding must expose a keyboard resize, such as arole="separator"with value attributes or a width field.
Focus
- Loading more rows must not move focus; new rows append after the focused one.
- Closing the column picker returns focus to the Columns button.
- Sticky headers must not cover a focused row when the body scrolls (2.4.11).
Other requirements
- The binding must announce when more rows have loaded, for example through a polite live region updating the loaded count (4.1.3).
- Column reorder and resize by drag need a keyboard or single-pointer alternative, such as move up and move down actions in the picker (2.5.7).
- The grid scrolls in both directions at narrow widths; 1.4.10 allows two-dimensional scrolling for data tables, but the toolbar must still reflow.
- Header sort buttons and picker checkboxes must meet 24 by 24 CSS pixels (2.5.8).
Test your implementation too. This guidance covers the element as specified here. How a screen composes it (labels, order, announcements) still needs its own keyboard and screen reader check.
Accessibility tests
Passed the gate (run 358b316 on 2026-10-22): no finding in any theme. Awaiting a person’s approval of the checklist below.
| Area | Automated | A person checks |
|---|---|---|
| General | Passed | Color is never the only cue, text alternatives say the same thing as the graphic, and the element reads the same in the light and the dark theme. |
| Zoom | Passed | At 200% zoom, at 320 CSS pixels wide and with the WCAG text-spacing overrides, nothing is lost, clipped or overlapping. |
| Keyboard | Passed | Every row of the Keyboard table works, focus is always visible and never trapped, and the tab order follows the reading order. (5 rows) |
| Screen reader | Passed | With NVDA or JAWS on Windows and VoiceOver on macOS, names, roles and states are announced as Roles and names says, and changes are announced once. (5 items) |
Approving. When the checklist passes on the checked run, record it with
bun run a11y:approve -- datagrid --by "Your name" and commit
a11y/approvals.json with the rebuilt pages.
All test status
Package
Consumers link one stylesheet, css/classic/index.css, which includes every element. The
files below are where this element is defined.
Latest updates
No recorded changes yet.