DataTable
A full-featured, column-definition driven data table with client-side sorting, row selection with indeterminate header checkbox, and pagination.
Installation
npx dafink-ui add data-tableAlso installs: Checkbox
Demo
| Status | Work type | |||||
|---|---|---|---|---|---|---|
| Alice Chen | Engineer | Platform | Active | $145,000 | ||
| Bob Ruiz | Designer | Product | Away | $130,000 | ||
| Carol Smith | Engineering Mgr | Platform | Active | $165,000 | ||
| Dave Park | Data Analyst | Analytics | Active | $120,000 | ||
| Eve Torres | Designer | Product | Active | $128,000 |
Props
| Name | Type | Default | Description |
|---|---|---|---|
| data | T[] | - | Array of data objects to display. |
| columns | ColumnDef<T>[] | - | Column definitions controlling headers, keys, sorting, and cell rendering. |
| keyField | keyof T & string | - | Property key used as a unique row identifier. |
| selectable | boolean | false | Prepends a checkbox column for row selection. |
| onSelectionChange | (selected: T[]) => void | undefined | Called with selected rows whenever selection changes. |
| pageSize | number | 10 | Rows per page. Set to 0 to show all rows. |
| emptyMessage | string | 'No data' | Text shown when data is empty. |
| className | string | '' | Additional classes on the root wrapper. |
Accessibility
DataTable is always a keyboard grid: the <table> renders role="grid" with roving tabindex, so it's a single Tab stop and Arrow keys move between cells instead of Tab stepping through every control one by one. Only a cell with something focusable inside it — a checkbox, a sort button, a row-action button, an inline field or composite widget from a render function — is ever a stop, reached with real, native focus on that element rather than the wrapping cell; a plain-text cell isn't a stop at all, and the Arrow keys skip straight past it (wrapping at the row/column ends) to the next cell that does have something focusable. If a whole row or column has nothing focusable in that direction, the Arrow key is a no-op. If nothing anywhere in the table is focusable, the grid isn't a Tab stop at all: Tab skips it entirely, same as a plain table. Because there's exactly one stop per grid position (or none), Enter/Space are handled natively by whatever's actually focused, with no handling of DataTable's own — a focused button responds to both, and so does the header/row Checkbox, since Checkbox itself adds Enter support (see its own Accessibility section) rather than this grid patching it in, so it works the same way wherever Checkbox is used. If a cell holds a composite widget like ToggleGroup or RadioGroup, Left/Right defer to that widget's own navigation (checking event.defaultPrevented) instead of also trying to move to a neighboring column, but Up/Down always belong to the grid regardless — those widgets treat Up/Down the same as Left/Right internally and, with 2+ items, always find a wrap target and claim the key too, so deferring the same way would trap you inside the cell with no way out; the grid intercepts Up/Down on the capture phase, before the widget's own handler ever sees them. Header and row checkboxes are the shared Checkbox component; the header one reflects a partial selection through the DOM indeterminate property, and each row checkbox has an aria-label naming its row. The numbered Paginator variant marks the current page with aria-current="page", and disabled Prev/Next buttons use the native disabled attribute so assistive tech skips them automatically.
| Key | Function |
|---|---|
| tab | Moves focus onto the grid at its current (or first) focusable cell, or off it entirely if already inside — a no-op if nothing in the grid is focusable. |
| shift+tab | Same, in reverse. |
| down arrow | Moves to the next focusable cell down in the same column, skipping any cell with nothing focusable in it and wrapping at the bottom. Always the grid’s, even over a focused composite widget’s own Down handling; no-op if there is no other focusable cell in that column. |
| up arrow | Moves to the next focusable cell up in the same column, skipping any cell with nothing focusable in it and wrapping at the top. Always the grid’s, even over a focused composite widget’s own Up handling; no-op if there is no other focusable cell in that column. |
| right arrow | Moves to the next focusable cell right in the same row, skipping any cell with nothing focusable in it and wrapping at the end; no-op if there is none. |
| left arrow | Moves to the next focusable cell left in the same row, skipping any cell with nothing focusable in it and wrapping at the start; no-op if there is none. |
| enter | Native activation for a focused sort or action button. A focused checkbox also toggles on Enter, but that’s Checkbox’s own behavior, not something this grid adds. |
| space | Native activation/toggle for whatever is focused — a button or a checkbox both already respond to Space with no extra handling needed. |