Selection, keyboard & clipboard
Row selection
const options = { /* ... */ selection: { mode: 'multiple', checkbox: true } };
// selection: { mode: 'single' }
Livewire: public bool $selectable = true;
A checkbox column is added (for multiple), with a header checkbox to select the
current page.
grid.toggleSelect(rowId);
grid.isSelected(rowId);
grid.selectAllOnPage();
grid.clearSelection();
grid.selectedIds();
Keyboard navigation & cell selection
The grid is a role="grid" with proper columnheader / gridcell roles,
aria-sort, aria-selected, and a roving tabindex.
| Key | Action |
|---|---|
| Arrow keys | Move the active cell |
| Home / End | First / last cell in the row |
| Ctrl/⌘ + Home / End | First / last cell in the grid |
| PageUp / PageDown | Move up / down by a page (viewport height) |
| Shift + Arrows | Extend a cell range |
| Click / Shift-Click | Set / extend the active range |
| Space | Toggle selection of the active row (selection on) |
| Ctrl/⌘ + A | Select all rows on the page (selection on) |
| Enter / F2 | Edit the active cell (in-cell editing) |
| Escape | Cancel the current edit |
| Ctrl/⌘ + C | Copy the selected range (TSV) |
| Ctrl/⌘ + V | Paste into editable cells (editing enabled) |
The active row is highlighted in a quiet grey (.dg-row-active) rather than
boxing a single cell, and a multi-cell range is highlighted when you extend a
selection. Typing in a filter or editor input is never intercepted by grid
navigation.
Programmatic (client core):
grid.setActiveCell(r, c); // extend? grid.setActiveCell(r, c, true)
grid.moveActive('ArrowDown', /* extend */ false);
grid.isActiveCell(r, c);
grid.inSelectionRange(r, c);
grid.copySelection(); // returns TSV string
await grid.pasteAt(tsvText); // applies to editable cells from the active cell
copySelection() builds the TSV from the data (not the DOM), so it copies exact
values.
Clipboard paste
With editing enabled, Ctrl/⌘ + V reads the clipboard, parses a TSV matrix, and
writes it into the editable columns starting at the active cell — one persisted
mutation per affected row. Non-editable columns in the paste range are ignored.
Row pinning
Pin rows to the top of the list:
grid.togglePinRow(rowId);
grid.isPinned(rowId);
Pinned rows render first. In the clients a pin button appears in the command column.
Pinning changes the display order, and that order is what row indices mean.
copySelection() and pasteAt() resolve the active cell against it (via
orderRows()), and the clients render from the same function — so what you copy
or paste into is always the row you see at that position.
Accessibility
Keyboard parity is part of the grid's contract, not a coat of paint:
- Menus (column list, header menu, filter operators) are real
<button>elements, so they are reachable by Tab and activated by Enter or Space. - Dialogs (row editor, filter builder) are
role="dialog" aria-modal="true", move focus inside on open, contain Tab and Shift+Tab, close on Escape, and return focus to whatever opened them. - Toggles rendered as spans — the select-all and select-row checkboxes, the
sort header, the group collapse control — carry
role="button",tabindex="0"and an accessible name, and respond to Enter and Space. They are spans rather than buttons only where a real button would break table layout. - Pointer-only affordances (column resize grips, drag-to-group) always have a keyboard equivalent in the column menu, and are marked presentational so they are not announced as controls.
e2e/tests/a11y.spec.ts asserts the focus trap and key activation in Chromium.