React
A shadcn table with TanStack Table
TanStack Table handles sorting and filters, the shadcn Table components draw the markup. Version 9 changed the API: useTable replaces useReactTable.
Key points
- TanStack Table is headless: it generates no markup, so the markup stays with the shadcn Table components.
- From 9.3.1 the API is useTable with tableFeatures; useReactTable becomes useLegacyTable.
- The package weighs 122,555 unpacked bytes and ships under the MIT licence.
Short answer: mount the shadcn Table components and hand data and columns to TanStack Table, which handles sorting, filters and pagination without generating markup. The table stays yours, the logic does not.
The columns
import {
createSortedRowModel,
rowSortingFeature,
sortFns,
tableFeatures,
} from "@tanstack/react-table";
import type { ColumnDef } from "@tanstack/react-table";
type Invoice = { id: string; total: number };
const features = tableFeatures({
rowSortingFeature,
sortedRowModel: createSortedRowModel(),
sortFns,
});
export const columns: Array> = [
{ accessorKey: "id", header: "Fattura" },
{
accessorKey: "total",
header: "Totale",
cell: (info) => info.getValue().toFixed(2) + " EUR",
},
];
Features are opt-in: tableFeatures receives only what you use, so sorting, filters and pagination never enter the bundle unless declared. The core row model is always there, the others register as slots.
The table
import { useTable } from "@tanstack/react-table";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { columns, features } from "./columns";
export function InvoiceTable({ data }: { data: Array }) {
const table = useTable({ key: "invoices", features, columns, data });
return (
{table.getHeaderGroups().map((group) => (
{group.headers.map((header) => (
{header.isPlaceholder ? null : }
))}
))}
{table.getRowModel().rows.map((row) => (
{row.getAllCells().map((cell) => (
))}
))}
);
}
The getToggleSortingHandler method comes from the sorting feature you just registered: without that line in tableFeatures a click changes nothing. FlexRender draws the cell with the function you wrote in the column, or prints the value.
Why two layers
The gain is separation: TanStack has no idea what a tr is, shadcn has no idea what sorting is. You can change the markup without rewriting the logic, or swap the markup for a list on mobile, and both pieces keep working.
The command
npm install @tanstack/react-table@9
npx shadcn@latest add table
Live records in the catalogue
Three records show the result: the shadcn library, the table-01 block with headers and pagination, and the table blocks category. The licence sits on every page.
The limitation
The package does not virtualise: a table with a thousand rows mounts a thousand nodes, and lightening it takes TanStack Virtual, another MIT package. Client-side sorting also works on data already downloaded; with ten thousand rows pagination has to come from the server, or the cost simply moves to the network.
Where to continue
The distribution model is in what shadcn/ui is; the most common flexRender error is in TanStack Table and flexRender; the comparison between libraries is in React table libraries.
Frequently asked questions
Why is useReactTable gone?
Because version 9 exposes useTable with tableFeatures. The previous API stays available from the legacy export of the package, with useLegacyTable in place of useReactTable.
Does TanStack Table generate the table?
No, it is headless: it returns state and functions, you write the markup. With shadcn you use Table, TableRow and TableCell, so styling stays inside the design system.
How do I add filtering?
Register the filtering feature and the filtered row model in tableFeatures, then bind an input to column.setFilterValue. Without that registration the filter API does not exist on the table.
Does it work with server-fetched data?
Yes, in manual mode: you pass the rows of the current page and let sorting and pagination be request parameters. The getSortedRowModel and getPaginationRowModel functions are not needed then.
TanStack Table or the shadcn Table components alone?
The Table components are enough for a static table with a few rows. TanStack Table earns its place once sorting, filtering, selection or pagination arrive, because that logic written by hand soon duplicates state.