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.jsonName | Email | Role | Status |
|---|---|---|---|
| Ava Thompson | ava.t@example.com | Admin | Active |
| Liam Chen | liam.chen@example.com | Editor | Active |
| Sofia Patel | sofia.p@example.com | Viewer | Inactive |
| Noah Garcia | noah.g@example.com | Editor | Active |
| Mia Johnson | mia.j@example.com | Admin | Pending |
| Ethan Kim | ethan.kim@example.com | Viewer | Active |
| Isabella Rossi | isabella.r@example.com | Editor | Active |
| Lucas Martin | lucas.m@example.com | Admin | Inactive |
| Amelia Novak | amelia.n@example.com | Viewer | Active |
| Mason Lee | mason.lee@example.com | Editor | Pending |
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.
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.
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.
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.
{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.
{table.state.columnFilters.length > 0 ? (<Button onClick={() => table.resetColumnFilters()}>Clear Filters</Button>) : null}
Using TanStack Table v8? See the v8 Data Table.