# Data table API

> Props, slots, labels and styling of the slotsmith Data table.

## Slots

`DataTable` has 24 slots. Pass a replacement for any of them in `components`; every slot you leave out keeps its fallback. Element slots receive DOM props, so a library's own element fits as it is; widget slots receive named props and usually need a few lines of adapter.

| Slot | Kind | Props type | Description |
| --- | --- | --- | --- |
| `Root` | element | `RootSlotProps` | Element slot: plain `<div>` props for the table's outer element. Carries `data-status`, `data-size`, `data-striped` and `aria-busy`. |
| `Table` | element | `TableSlotProps` | Element slot: plain `<table>` props. |
| `Head` | element | `SectionSlotProps` | Element slot: plain `<thead>` / `<tbody>` props. |
| `Body` | element | `SectionSlotProps` | Element slot: plain `<thead>` / `<tbody>` props. |
| `HeaderRow` | element | `RowSlotProps` | Element slot: plain `<tr>` props. Body rows carry `data-state="selected"`, `data-depth`, `data-expanded` and `data-clickable`; status rows carry `data-state="loading" \| "empty" \| "error"`. Read the row itself with `useDataTableRow()`. |
| `HeaderCell` | element | `HeaderCellSlotProps` | Element slot: plain `<th>` props. Sortable headers carry `aria-sort` and `data-sorted`; aligned columns carry `data-align`. The obsolete HTML `align` attribute is left out (alignment is set through `style`), so components with their own `align` prop, like MUI's `TableCell`, fit directly. |
| `Foot` | element | `SectionSlotProps` | Element slot: plain `<thead>` / `<tbody>` props. |
| `FooterRow` | element | `RowSlotProps` | Element slot: plain `<tr>` props. Body rows carry `data-state="selected"`, `data-depth`, `data-expanded` and `data-clickable`; status rows carry `data-state="loading" \| "empty" \| "error"`. Read the row itself with `useDataTableRow()`. |
| `FooterCell` | element | `CellSlotProps` | Element slot: plain `<td>` props. Aligned columns carry `data-align`. The obsolete HTML `align` attribute is left out, as for HeaderCellSlotProps. |
| `Row` | element | `RowSlotProps` | Element slot: plain `<tr>` props. Body rows carry `data-state="selected"`, `data-depth`, `data-expanded` and `data-clickable`; status rows carry `data-state="loading" \| "empty" \| "error"`. Read the row itself with `useDataTableRow()`. |
| `Cell` | element | `CellSlotProps` | Element slot: plain `<td>` props. Aligned columns carry `data-align`. The obsolete HTML `align` attribute is left out, as for HeaderCellSlotProps. |
| `Checkbox` | widget | `CheckboxSlotProps` | Widget slot for the row and select-all checkboxes. |
| `SortTrigger` | widget | `SortTriggerSlotProps` | Widget slot wrapping a sortable header's content. The fallback is a `<button>` holding the header and the `SortIcon` slot. |
| `SortIcon` | widget | `SortIconSlotProps` | Widget slot for the icon inside the fallback `SortTrigger`. |
| `ExpandToggle` | widget | `ExpandToggleSlotProps` | Widget slot for the button that expands a tree row, shown in the first column. |
| `Skeleton` | widget | `SkeletonSlotProps` | Widget slot rendered in every cell while `loading` is true. |
| `Empty` | widget | `EmptySlotProps` | Widget slot rendered in a full-width cell when there are no rows. |
| `Error` | widget | `ErrorSlotProps` | Widget slot rendered in a full-width cell when `error` is set. |
| `Pagination` | widget | `PaginationSlotProps` | Widget slot for the whole pagination bar. The fallback composes the `PaginationButton` and `PageSizeSelect` slots. |
| `PaginationButton` | widget | `PaginationButtonSlotProps` | Widget slot for the previous / next buttons of the fallback `Pagination`. |
| `PageSizeSelect` | widget | `PageSizeSelectSlotProps` | Widget slot for the rows-per-page picker of the fallback `Pagination`. The fallback is the slotsmith `Autocomplete` as a plain single select, labelled by `label` and following the table's `locale`, so the data table's bundle includes the autocomplete. Replace the slot to use your own select. |
| `DragHandle` | element | `DragHandleSlotProps` | Element slot: plain `<button>` props for the handle a row is dragged by when `enableRowReorder` is on. The table supplies the accessible name, the pointer and key handlers and a ref, so a library's icon button that spreads its props works as is. The `color` attribute is left out so buttons with their own `color` prop fit. Carries `data-dragging` while its row is moving. |
| `CellEditor` | widget | `CellEditorSlotProps` | Widget slot for editing a cell in spreadsheet mode, one kind at a time. Every value is text: the column's `parse` turns it into the row's value. The table moves focus into the editor when it opens, and handles Enter, Tab, Escape and the arrow keys from it, unless the editor prevents the key's default (an open popup's own keys). Render it with no outer margin: it sits inside the cell. |
| `FillHandle` | element | `FillHandleSlotProps` | Element slot: plain `<span>` props for the small square on the selection's corner in spreadsheet mode. Dragging it fills the cells it is dragged over. It carries `data-fill-handle`, which the table finds it by. |

## Props

Every prop of `DataTable`, grouped as in the library's documentation. Controlled state comes in pairs: pass a value with its change handler, or only the default to leave the state inside the component.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `T[]` |  | The rows to display. For manual pagination, just the current page. Pass this or `value`. |
| `value` | `T[]` |  | The rows, for a table people edit: the same as `data`, named like any other controlled value. Wins over `data`. |
| `columns` | `DataTableColumnDef<T>[]` |  | The column definitions. |
| `getRowId` | `(row: T, index: number, parent?: DataTableRow<T>) => string` | ``row.id`, falling back to the row's index path` | Stable row identity. Defaults to `row.id`, falling back to the row's index path. |
| `sorting` | `SortingState` |  | Controlled sorting state. |
| `onSortingChange` | `(sorting: SortingState) => void` |  | Called with the new sorting state. |
| `defaultSorting` | `SortingState` | `[]` | Initial sorting when uncontrolled. Defaults to `[]`. |
| `manualSorting` | `boolean` | `false` | The data arrives already sorted (server-side sorting). Defaults to `false`. |
| `enableMultiSort` | `boolean` | `false` | Allow sorting by several columns with shift-click. Defaults to `false`. |
| `pagination` | `PaginationState` |  | Controlled pagination state. |
| `onPaginationChange` | `(pagination: PaginationState) => void` |  | Called with the new pagination state. |
| `defaultPagination` | `PaginationState` | `{ pageIndex: 0, pageSize: 10 }` | Initial pagination when uncontrolled. Defaults to `{ pageIndex: 0, pageSize: 10 }`. |
| `manualPagination` | `boolean` | `false` | The data is already one page (server-side pagination); pass `rowCount` too. Defaults to `false`. |
| `rowCount` | `number` |  | Total rows across all pages, for manual pagination. |
| `pageSizeOptions` | `number[]` | `DEFAULT_PAGE_SIZE_OPTIONS` | The page sizes offered. Defaults to DEFAULT_PAGE_SIZE_OPTIONS. |
| `enablePagination` | `boolean` | `true` | `false` renders every row and hides the pagination. Defaults to `true`. |
| `hidePagination` | `boolean` |  | Hide the pagination UI while still paginating. |
| `enableRowSelection` | `boolean \| ((row: DataTableRow<T>) => boolean)` |  | Shows a checkbox column. A function decides per row whether it can be selected. |
| `selection` | `T[]` |  | Controlled selection, as row objects. Matched to rows by `getRowId`. |
| `onSelectionChange` | `(selection: T[]) => void` |  | Called with the selected rows, including rows on other server pages. |
| `defaultSelection` | `T[]` | `[]` | Initial selection when uncontrolled. Defaults to `[]`. |
| `resetSelectionOnPageChange` | `boolean` | `false` | Clear the selection whenever the page or page size changes. Defaults to `false`. |
| `getSubRows` | `(row: T, index: number) => readonly T[] \| undefined` |  | Returns a row's children, turning the table into a tree. |
| `setSubRows` | `(row: T, subRows: T[]) => T` |  | Writes new children into a copy of a tree row and returns it: how the table builds the new tree after a move across parents (`reorderAcrossParents`) or an edit of a sub-row in spreadsheet mode. |
| `getRowCanExpand` | `(row: DataTableRow<T>) => boolean` |  | Decides whether a row can expand, e.g. to lazy-load children. |
| `expanded` | `ExpandedState` |  | Controlled expansion: `true` for all rows, or `{ [rowId]: true }`. |
| `onExpandedChange` | `(expanded: ExpandedState) => void` |  | Called with the new expansion state. |
| `defaultExpanded` | `ExpandedState` | `{}` | Initial expansion when uncontrolled. Defaults to `{}`. |
| `paginateExpandedRows` | `boolean` | `false` | Count sub-rows towards the page size. Defaults to `false`. |
| `enableRowReorder` | `boolean` | `false` | Lets rows be dragged into a new order, by pointer, touch or keyboard. In a tree table (`getSubRows`) a row moves among its siblings only, with its sub-rows. Ignored, with a development warning, in `VirtualDataTable`. Defaults to `false`. |
| `onRowOrderChange` | `(change: RowOrderChange<T>) => void` |  | Called when a row is dropped in a new place. The table does not reorder itself: store `change.data`, or for a sub-row, `change.siblings` as the children of `change.parent`. |
| `reorderHandleColumn` | `boolean` | `true` | Adds a leading column with each row's drag handle. `false` lets you place `DataTable.DragHandle` yourself. Defaults to `true`. |
| `reorderWhileSorted` | `ReorderWhileSorted` | `"clearSort"` | What a drop means while a column sort is active. `"clearSort"`: the drop reorders the data and the sort is cleared (through `onSortingChange`), so the new order shows. `"block"`: the handles are disabled until the sort is cleared. Defaults to `"clearSort"`. |
| `reorderAcrossParents` | `boolean` | `false` | In a tree table, rows may be dragged to another parent and another depth, not only among their siblings: drag sideways (or press the left and right arrow keys while a row is lifted) to move it in or out a level. Needs `getSubRows` and `setSubRows`; a drop then reports the whole new tree as `change.data`. Defaults to `false`. |
| `canDrop` | `(move: RowMove<T>) => boolean` |  | Whether a dragged row may land at a place; places it may not are skipped while dragging. |
| `reorderGroup` | `string` |  | Tables with the same `reorderGroup` exchange rows: a top-level row (or a selected group of them) dragged out of one table can be dropped into another, by pointer, or from the keyboard past the first or last row. Each table needs `enableRowReorder`. |
| `reorderTableId` | `string` | `a generated id` | This table's name in transfers between the tables of a reorder group. Defaults to a generated id. |
| `onRowTransfer` | `(transfer: RowTransfer<T>) => void` |  | Called on both tables when rows move between them: `transfer.data` is this table's new rows (without the rows in the table they left, with them in the one they joined). Store it, as with `onRowOrderChange`. |
| `spreadsheet` | `boolean \| DataTableSpreadsheetOptions<T>` | `false` | Spreadsheet mode: cells are selected, navigated and edited with the keyboard and the pointer, copied and pasted as tab-separated text, filled with the fill handle and undone. Columns opt in to editing with `edit`. Defaults to `false`. |
| `onValueChange` | `(value: T[], change: DataTableValueChange<T>) => void` |  | Called with the new rows after every edit in spreadsheet mode: typing, paste, cut, Delete, fill, undo and redo. The table never changes the rows itself, so store `value` and pass it back. Only the rows that changed are new objects, so only they re-render. |
| `loading` | `boolean` | `false` | Shows skeleton rows. Defaults to `false`. |
| `error` | `unknown` |  | Any non-nullish value shows the error state. |
| `onRetry` | `() => void` |  | Shows a retry button in the error state. |
| `components` | `Partial<DataTableComponents>` |  | Replace any UI slot; the rest fall back to `SlotsmithProvider`'s `components.dataTable`, then to the built-in plain-HTML ones. |
| `labels` | `Partial<DataTableLabels>` |  | Override any text, e.g. to translate the table. Wins over `locale`. |
| `locale` | `LocaleInput` | `the provider's` | The language: a locale object, or the tag of one given to `SlotsmithProvider`. Defaults to the provider's. |
| `slotProps` | `DataTableSlotProps<T>` |  | Extra DOM props for element slots, e.g. per-row classNames. |
| `onRowClick` | `DataTableContextValue<T>["onRowClick"]` |  | Called when a body row is clicked (not its checkbox or expand toggle). |
| `size` | `"sm" \| "default"` |  | Density; sets `data-size`. |
| `striped` | `boolean` |  | Alternate row backgrounds; sets `data-striped`. |
| `tableOptions` | `Partial<TableOptions<DataTableFeatures, T>>` |  | Escape hatch: extra options passed straight to TanStack's `useTable`. |
| `virtual` | `DataTableVirtualOptions` |  | Renders only the rows in view, for long lists: pass the virtualizer hook (`useVirtualizer` from `@tanstack/react-virtual`) as `use`, and the sizes. The header stays sticky, and pagination is off unless `enablePagination` turns it back on. Every other feature keeps working, row reordering and spreadsheet mode included. |

## Labels & locales

Every string `DataTable` shows or announces, with its English default. Pass `labels` to change some of them for one component, or `locale` to switch every string to another language. The demo below does both.

| Label | Type | Default | Description |
| --- | --- | --- | --- |
| `empty` | `ReactNode` | `"No data found"` | Empty state message. Default: "No data found". |
| `error` | `ReactNode` | `"Something went wrong while loading the data."` | Error state message. |
| `retry` | `ReactNode` | `"Retry"` | Retry button text. Default: "Retry". |
| `rowsPerPage` | `ReactNode` | `"Rows per page"` | Page-size select label. Default: "Rows per page". |
| `pageInfo` | `(page: number, pageCount: number) => ReactNode` | `(page, pageCount) => `Page ${page} of ${pageCount}`` | Page info. Default: `Page ${page} of ${pageCount}`. |
| `pagination` | `string` | `"Pagination"` | Accessible name of the pagination landmark. |
| `previousPage` | `string` | `"Previous page"` | Accessible name of the previous-page button. |
| `nextPage` | `string` | `"Next page"` | Accessible name of the next-page button. |
| `selectAll` | `string` | `"Select all rows on this page"` | Accessible name of the select-all checkbox. |
| `selectRow` | `string` | `"Select row"` | Accessible name of each row checkbox. |
| `expandRow` | `string` | `"Expand row"` | Accessible name of the expand toggle on a collapsed row. |
| `collapseRow` | `string` | `"Collapse row"` | Accessible name of the expand toggle on an expanded row. |
| `reorderRow` | `string` |  | Accessible name of each row's drag handle. Default: "Reorder row". |
| `reorderInstructions` | `string` |  | How to move a row with the keyboard, given to assistive technology with the drag handle. |
| `reorderLifted` | `(position: number, total: number) => string` |  | Announced when a row is lifted. Default: `Row lifted. Position ${position} of ${total}.` |
| `reorderMoved` | `(position: number, total: number) => string` |  | Announced on each keyboard step. Default: `Position ${position} of ${total}.` |
| `reorderDropped` | `(position: number, total: number) => string` |  | Announced when a row is dropped. Default: `Row dropped at position ${position} of ${total}.` |
| `reorderCancelled` | `string` |  | Announced when a move is cancelled. Default: "Reordering cancelled." |
| `reorderLevel` | `(level: number) => string` |  | Announced after a step or a drop that moves a row across parents, with its depth from 1. Default: `Level ${level}.` |
| `editCell` | `(column: string) => string` |  | Spreadsheet mode: the cell editor's accessible name. Default: `Edit ${column}`. |
| `invalidNumber` | `string` |  | Spreadsheet mode: a number cell got text that is not a number. Default: "Enter a number." |
| `invalidOption` | `string` |  | Spreadsheet mode: a select cell got text that matches no option. Default: "Choose one of the options." |
| `invalidDate` | `string` |  | Spreadsheet mode: a date cell got text that is not a date. Default: "Enter a valid date." |
| `invalidBoolean` | `string` |  | Spreadsheet mode: a checkbox cell got text that is neither true nor false. Default: "Enter TRUE or FALSE." |
| `cellsCopied` | `(count: number) => string` |  | Spreadsheet mode: announced after a copy. Default: `${count} cells copied.` |
| `cellsCut` | `(count: number) => string` |  | Spreadsheet mode: announced after a cut. Default: `${count} cells cut.` |
| `cellsPasted` | `(count: number, skipped: number) => string` |  | Spreadsheet mode: announced after a paste; `skipped` cells did not fit their column. |
| `cellsCleared` | `(count: number) => string` |  | Spreadsheet mode: announced after Delete. Default: `${count} cells cleared.` |
| `cellsFilled` | `(count: number) => string` |  | Spreadsheet mode: announced after a fill. Default: `${count} cells filled.` |
| `undone` | `string` |  | Spreadsheet mode: announced after an undo. Default: "Undone." |
| `redone` | `string` |  | Spreadsheet mode: announced after a redo. Default: "Redone." |

## Styling

The class names the fallbacks of `DataTable` put on their elements, and the tokens its stylesheet reads. Set a token on the component, or on any element around it, to restyle it; target a class only for what no token covers.

Classes: `sdt`, `sdt__scroll`, `sdt__table`, `sdt__head`, `sdt__body`, `sdt__foot`, `sdt__row`, `sdt__cell`, `sdt__cell--drag`, `sdt__cell--select`, `sdt__checkbox`, `sdt__sort`, `sdt__sort-icon`, `sdt__tree`, `sdt__tree-spacer`, `sdt__expand`, `sdt__skeleton`, `sdt__message`, `sdt__error`, `sdt__button`, `sdt__button--icon`, `sdt__pagination`, `sdt__pagination-buttons`, `sdt__pagination-info`, `sdt__page-size`, `sdt__page-size-select`, `sdt__drag`, `sdt__editor-host`, `sdt__cell-ghost`, `sdt__checkbox-cell`, `sdt__editor`, `sdt__editor-input`, `sdt__editor-control`, `sdt__editor-trigger`, `sdt__editor-error`, `sdt__fill-handle`, `sdt__sr-only`

Tokens: `--sdt-accent`, `--sdt-bg`, `--sdt-border`, `--sdt-checkbox-size`, `--sdt-danger`, `--sdt-font-size`, `--sdt-header-bg`, `--sdt-hover`, `--sdt-max-height`, `--sdt-muted`, `--sdt-padding-x`, `--sdt-padding-y`, `--sdt-radius`, `--sdt-selected`, `--sdt-skeleton-bg`, `--sdt-skeleton-bg-2`, `--sdt-stripe`, `--sdt-surface`, `--sdt-text`, `--ss-accent`, `--ss-border`, `--ss-danger`, `--ss-font-size`, `--ss-hover`, `--ss-muted`, `--ss-radius`, `--ss-selected`, `--ss-surface`, `--ss-text`
