State management
The data table has two layers of state, and both can be left uncontrolled, observed, or fully controlled.
TanStack table state
All the standard table state — sorting, column filters, global filter, pagination, row selection, column visibility, ordering, pinning, sizing, expansion, grouping — is plain TanStack Table state. useDataTable spreads its options straight into useReactTable, so you control it exactly as you would in TanStack: pass state + the matching onXChange setter (and optionally initialState for uncontrolled seeds).
const [sorting, setSorting] = React.useState<SortingState>([])
const [rowSelection, setRowSelection] = React.useState({})
const table = useDataTable({
data,
columns,
getRowId: (row) => row.id,
state: { sorting, rowSelection },
onSortingChange: setSorting,
onRowSelectionChange: setRowSelection,
})
For server-side data, combine this with the manual* flags — see Server-side data.
Data table UI state
The presentation state the table adds on top of TanStack lives on table.cnTable: density, full screen, filter-row visibility, and the global search mode. Each is uncontrolled by default but accepts a controlled value and/or an onChange callback — so you can observe a change without taking ownership, or take full control.
const table = useDataTable({
data,
columns,
getRowId: (row) => row.id,
// Uncontrolled with a seed:
defaultDensity: "compact",
defaultShowColumnFilters: true,
defaultGlobalFilterMode: "fuzzy",
// Observe changes (still uncontrolled):
onDensityChange: (d) => saveToLocalStorage("density", d),
// Or take control:
isFullscreen,
onIsFullscreenChange: setIsFullscreen,
})
| Prop | Type | Default | Description |
|---|---|---|---|
defaultDensity? | Density | "comfortable" | Initial density. Uncontrolled. |
density? | Density | — | Controlled density. Pair with `onDensityChange`; omit for uncontrolled (seed the initial value with `defaultDensity`). |
onDensityChange? | (density: Density) => void | — | Called whenever the density changes (toolbar toggle or programmatic). |
isFullscreen? | boolean | — | Controlled full-screen state. Pair with `onIsFullscreenChange`. |
onIsFullscreenChange? | (isFullscreen: boolean) => void | — | Called whenever the full-screen state is toggled. |
defaultShowColumnFilters? | boolean | false | Initially show the filter row. Uncontrolled. |
showColumnFilters? | boolean | — | Controlled filter-row visibility. Pair with `onShowColumnFiltersChange`; omit for uncontrolled (seed with `defaultShowColumnFilters`). |
onShowColumnFiltersChange? | (showColumnFilters: boolean) => void | — | Called whenever the filter row is shown or hidden. |
defaultGlobalFilterMode? | GlobalFilterMode | "fuzzy" | Initial global search mode. Default "fuzzy". |
globalFilterMode? | GlobalFilterMode | — | Controlled global search mode. Pair with `onGlobalFilterModeChange`; omit for uncontrolled (seed with `defaultGlobalFilterMode`). |
onGlobalFilterModeChange? | (mode: GlobalFilterMode) => void | — | Called whenever the global search mode changes. |
Reading and setting from the instance
Whether controlled or not, the current values and setters are always available on table.cnTable:
table.cnTable.density // "comfortable" | "compact" | "spacious"
table.cnTable.setDensity("compact")
table.cnTable.setIsFullscreen(true)
table.cnTable.setShowColumnFilters((prev) => !prev)
Persisting state
Mirror any of these into local storage or a query param, then restore via the
matching default* (uncontrolled) or controlled value on the next load.
Styling without props
The table is styled with Tailwind, not per-component style props. Structural slots emit a data-slot attribute you can target with a selector — see the table in Toolbar customization.