Shadcn React Table

Search documentation

Search the docs

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,
})
PropTypeDefaultDescription
defaultDensity?Density"comfortable"Initial density. Uncontrolled.
density?DensityControlled density. Pair with `onDensityChange`; omit for uncontrolled (seed the initial value with `defaultDensity`).
onDensityChange?(density: Density) => voidCalled whenever the density changes (toolbar toggle or programmatic).
isFullscreen?booleanControlled full-screen state. Pair with `onIsFullscreenChange`.
onIsFullscreenChange?(isFullscreen: boolean) => voidCalled whenever the full-screen state is toggled.
defaultShowColumnFilters?booleanfalseInitially show the filter row. Uncontrolled.
showColumnFilters?booleanControlled filter-row visibility. Pair with `onShowColumnFiltersChange`; omit for uncontrolled (seed with `defaultShowColumnFilters`).
onShowColumnFiltersChange?(showColumnFilters: boolean) => voidCalled whenever the filter row is shown or hidden.
defaultGlobalFilterMode?GlobalFilterMode"fuzzy"Initial global search mode. Default "fuzzy".
globalFilterMode?GlobalFilterModeControlled global search mode. Pair with `onGlobalFilterModeChange`; omit for uncontrolled (seed with `defaultGlobalFilterMode`).
onGlobalFilterModeChange?(mode: GlobalFilterMode) => voidCalled 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.