Search docs

Jump to any table example

Discord—

Shadcn Data Table — TanStack Table v9

The same sortable, filterable, paginated table as the v8 version, ported to TanStack Table v9's useTable and tableFeatures API. Behavior is unchanged — sorting, filtering, and pagination still run entirely in the browser.

$ npx shadcn add https://www.shad-table.dev/r/data-table-v9.json
Name
Email
Role
Status
Ava Thompsonava.t@example.comAdminActive
Liam Chenliam.chen@example.comEditorActive
Sofia Patelsofia.p@example.comViewerInactive
Noah Garcianoah.g@example.comEditorActive
Mia Johnsonmia.j@example.comAdminPending
Ethan Kimethan.kim@example.comViewerActive
Isabella Rossiisabella.r@example.comEditorActive
Lucas Martinlucas.m@example.comAdminInactive
Amelia Novakamelia.n@example.comViewerActive
Mason Leemason.lee@example.comEditorPending

What changed from v8

1.Row models come from a shared `features` object, not table options

In v8 each getXRowModel() was passed directly to useReactTable. In v9 they're feature slots registered once in tableFeatures() and reused across every table — features is a stable module-scope object, not recreated per render.

src/lib/table-v9-features.ts
export const v9Features = tableFeatures({
...stockFeatures,
filteredRowModel: createFilteredRowModel(),
sortedRowModel: createSortedRowModel(),
paginatedRowModel: createPaginatedRowModel(),
// ...
filterFns,
sortFns,
aggregationFns,
})

2.`useReactTable` becomes `useTable`, `features` is required

The hook is renamed and now takes a features option built from tableFeatures(). getCoreRowModel is gone — the core row model is automatic.

src/components/v9/basic/data-table.tsx
const table = useTable({
features: v9Features,
data,
columns,
onPaginationChange: setPagination,
state: { pagination, sorting, globalFilter, columnFilters },
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onGlobalFilterChange: setGlobalFilter,
})

3.Columns are built with a features-aware column helper

createColumnHelper now takes typeof features as its first type parameter so filterFn/sortFn string values are checked against the registries declared in features. sortingFn is renamed sortFn.

src/components/v9/basic/columns.tsx
const columnHelper = createColumnHelper<typeof v9Features, User>()
export const columns = columnHelper.columns([
columnHelper.accessor('role', {
header: 'Role',
sortFn: 'basic',
filterFn: 'equalsString',
}),
])

4.`flexRender` becomes `table.FlexRender`

The standalone flexRender(def, context) call still exists, but v9's idiomatic form is the table.FlexRender component, bound to the table instance.

src/components/v9/basic/data-table.tsx
{header.isPlaceholder ? null : <table.FlexRender header={header} />}
// ...
<table.FlexRender cell={cell} />

5.`table.getState()` becomes `table.state`

State is a direct property instead of a method call. Per-slice state and onXChange callbacks work exactly like v8 — this table still owns pagination/sorting/filters as controlled React state.

src/components/v9/basic/data-table.tsx
{table.state.columnFilters.length > 0 ? (
<Button onClick={() => table.resetColumnFilters()}>Clear Filters</Button>
) : null}

Using TanStack Table v8? See the v8 Data Table.