DataTable
import { DataTable } from '@tangible/ui';
Interactive docs and examples: DataTable in Storybook.
DataTable props
| Prop | Type | Default | Description |
|---|---|---|---|
columns |
ColumnDef<T, unknown>[] |
TanStack column definitions. The selection column is injected, not declared. | |
data |
T[] |
Row data. Memoize it, or pass autoResetPageIndex={false} — see below. |
|
aria-label? |
string |
Defines a string value that labels the current element. | |
aria-labelledby? |
string |
Identifies the element (or elements) that labels the current element. | |
autoResetPageIndex? |
boolean |
TanStack's automatic page-0 reset when sorting or data identity change. Defaults to true when pageIndex is uncontrolled, false when controlled — a controlled parent owns the index, and the auto-reset would race it back to 0 on every re-render that recreates data (parent re-renders on page change → new array identity → reset). Uncontrolled consumers who setState inside onPageIndexChange while passing a derived (unmemoized) data array hit the same loop: pass false here or memoize data. |
|
className? |
string |
||
defaultSorting? |
SortingState |
[] |
Initial sort state. Sorting stays internal — this only seeds it. |
emptyMessage? |
React.ReactNode |
'No results' |
Shown in place of rows when data is empty. |
enableRowSelection? |
boolean |
false |
|
getRowId? |
(row: T, index: number) => string | number |
||
getRowLabelForSelection? |
(row: T, index: number) => string |
||
getRowProps? |
( row: TanRow<T> ) => Omit<React.ComponentProps<'tr'>, 'key' | 'children' | 'dangerouslySetInnerHTML'> |
Extra props for each body <tr> — the escape hatch for row-level interaction: keyboard cursors, row click shortcuts, data-* hooks, context menus. Consumer className is merged after the built-in row classes; data-selected and the is-selected class always reflect actual selection state and cannot be overridden. role/aria-* are NOT guarded — overriding role on a <tr> removes it from the table's accessibility tree, so only do that as part of a deliberate pattern (e.g. rows in a role="grid" table keep role="row"). Loading/empty placeholder rows are not affected. |
|
initialPageSize? |
number |
||
labels? |
DataTableLabels |
Override hardcoded English strings for i18n. Unset keys use English defaults. | |
loading? |
boolean |
false |
Replaces the body with a loading row while true. |
loadingMessage? |
React.ReactNode |
'Loading...' |
Shown in place of rows while loading. |
onPageIndexChange? |
(pageIndex: number) => void |
Fires with the requested 0-based page index whenever anything asks for a page change — pager clicks, page-size changes (TanStack re-anchors to keep the top row visible), the auto page-0 reset (see autoResetPageIndex), or an out-of-range controlled pageIndex being clamped into range. Fires in both controlled and uncontrolled modes. Note: 0-based, unlike Pager's 1-based onPageChange (the built-in pager converts). |
|
onRowSelectionChange? |
(updater: Updater<RowSelectionState>) => void |
||
onSelectionChange? |
(selectedRows: T[], selectedIds: (string | number)[]) => void |
||
pageIndex? |
number |
Controlled page index (0-based, matching TanStack). When set, the parent owns paging: the built-in pager requests changes via onPageIndexChange, but the page only moves when this prop updates. Page size stays internal either way. Controlled/uncontrolled is locked at mount. |
|
pageSizeOptions? |
number[] |
[10, 20, 50] |
|
paginationMaxNumbers? |
number |
7 |
|
paginationMode? |
"ends" | "full" | "simple" | "smart" |
'smart' |
|
paginationNavStyle? |
"icon" | "text" |
'text' |
|
renderBulkBar? |
(args: BulkSelectionRenderArgs<T>) => React.ReactNode |
||
renderPagination? |
(table: TanTable<T>) => React.ReactNode |
||
rowSelection? |
RowSelectionState |
Also accepts the standard attributes of TableHTMLAttributes (data-*, aria-*, event handlers), passed to the element it renders.
Examples
-
Clickable Rows: Whole-row click as a pointer shortcut via
getRowProps. The row click must duplicate an accessible control (here the View button), never replace it — a<tr>with only anonClickis invisible to keyboard and AT. The handler also ignores clicks that land on the row's own interactive content. -
Keyboard Cursor: A consumer-owned keyboard cursor composed from
getRowProps(rovingtabIndex,data-row-id, accessible row names, Enter/Space activation on keydown) and controlled pagination (pageIndex/onPageIndexChange) — arrowing past the last row of a page flips to the next page and lands on its first row. The table is promoted torole="grid"(rest props spread onto the<table>), the APG signal that arrow keys belong to the app rather than the screen reader's browse cursor; the cursor ring uses the shared--tui-focus-ring-*tokens via:focus-visible, so it's keyboard-only and follows context remaps. Cursor movement reads DOM sibling order, not data order, so it stays correct under sorting; the row query relies on.tui-table__row--body, a stable class contract. The cursor deliberately does not touchrowSelection, which means "checked for bulk actions". -
Controlled Pagination: When
pageIndexis set the parent owns paging: the built-in pager requests changes throughonPageIndexChangeand the page moves only when the prop updates. Page size stays internal.onPageIndexChangealso fires on the automatic reset to page 0 when sorting or data change. -
Loading: While
loadingis true the table setsaria-busyand showsloadingMessagein place of rows. Column headers stay mounted so the layout doesn't jump when data arrives. -
Content Escaping: Cell values render as text, never as HTML — markup, template syntax, and entities in the data appear literally. Rich cell content goes through column
cellrenderers as React elements, not strings. -
Localised Labels: Every announced string routes through
labels: selection checkboxes, sort buttons, and the live-region announcements for page changes and sorting. The scrollable container is a namedrole="region"(viaaria-label/aria-labelledby) and is keyboard-focusable so keyboard users can scroll wide tables.
Other stories: Basic, Empty, With Actions, With Bulk Select (interactive page).
Component API tokens
Read via fallback, never declared. Set one on an ancestor to restyle every instance under it.
--tui-table-border--tui-table-bg--tui-table-bg-head--tui-table-fg--tui-table-padding--tui-table-selected-bg--tui-table-selected-fg