Production guides
Own the data model. Compose the interface.
Use these patterns as a launch checklist for local and server-windowed tables.
Local data
Pass the complete array through dataSource. Local sorting and filtering run in the worker; onChange receives edits and row operations.
<DataTable dataSource={rows} onChange={setRows}
columnSettings={columns} enablePagination showGlobalSearch />Remote windows
The host owns order, filters, cache, and mutations. Return cached rows from getRow, increment dataVersion after filling a window, and fetch onVisibleRangeChange with overscan.
<DataTable rowCount={total} getRow={(index) => cache.get(index)}
dataVersion={cacheVersion} onVisibleRangeChange={loadWindow}
onSortChange={setRemoteSort} onFilterChange={setRemoteQuery}
columnSettings={columns} />Columns and custom cells
Keep columnSettings memoized. Use accessor for derived values, cell for JSX, searchValue for search text, and exportValue for portable output.
const columns: ColumnSetting<Order>[] = [
{ id: "total", title: "Total", accessor: row => row.amount,
cell: ({ value }) => <Money value={Number(value)} />,
exportValue: row => row.amount },
];Editing and validation
Enable editing once, then opt columns into built-in or custom editors. Validation uses Zod; local changes use onChange and remote changes use onCellsEdit.
{ id: "email", editor: "email",
validation: z => z.string().email("Enter a valid email") }Selection and expanded rows
Checkbox selection is independent from the active cell. Supplying renderExpandedRow adds a +/- cell left of the checkbox. The original row stays visible and a measured, full-width detail row is inserted below it.
<DataTable enableRowSelection renderExpandedRow={({ row, collapse }) => (
<OrderDetails order={row} onClose={collapse} />
)} defaultExpandedRows={[2]} />Layout persistence
Store onColumnSettingsChange snapshots by user and table key. Validate saved column ids against the current schema before restoring them.
Import and export
Enable CSV upload/download for local data. Remote tables should supply getRowsForExport. Define exportValue for rich cells and prefer CSV for very large exports. When ExcelJS is installed, XLSX uses the built-in lazy adapter; pass xlsxAdapter only to customize that integration.
Tailwind, tokens, and unstyled mode
Use tokens for a coherent theme and classNames for semantic surfaces. unstyled removes the visual preset but retains structural, virtualized, accessible behavior. See the complete styling reference.
<DataTable
tokens={{ "--dt-accent": "#7c3aed", "--dt-expanded-border": "#ddd6fe" }}
classNames={{ root: "rounded-xl shadow-sm",
expandedPanel: "bg-violet-50 dark:bg-violet-950" }}
/><DataTable unstyled classNames={{
root: "border border-slate-200 bg-white text-slate-950",
body: "[&_.dt-cell]:border-slate-200 [&_.dt-header-cell]:font-semibold",
menu: "rounded-lg border bg-white shadow-xl"
}} />Accessibility
Give the table a meaningful title, preserve focus outlines, label custom controls, and keep expanded content keyboard reachable. The expander exposes aria-expanded and aria-controls; custom panel JSX owns its internal semantics.
Performance
Memoize columns and callbacks, use stable row identity, avoid heavy synchronous cell renderers, and batch remote cache updates. Expanded panels use ResizeObserver, including async content resizing.
Responsive behavior
Provide a measurable width and height. Horizontal virtualization preserves columns; seed essential columns with widths and let users hide secondary fields through column settings.
Production checklist
- Choose local or remote ownership.
- Memoize schema and callbacks.
- Test keyboard editing, selection, expansion, and validation.
- Persist compatible layouts.
- Exercise dark, custom-token, and unstyled themes.
- Test narrow screens, large data, import/export, and loading failures.