Data Table
A sortable, filterable, paginated table with a composable toolbar for column-specific filters. Sorting, filtering, and pagination all happen entirely in the browser — the whole dataset is sent to the client once, and TanStack Table slices it locally.
npx shadcn add https://shad-table.dev/r/data-table.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 |
How it works
1.The whole dataset lives on the client
data is passed straight in as a prop — there's no server call. getFilteredRowModel, getSortedRowModel, and getPaginationRowModel all run in the browser against that full in-memory array.
const table = useReactTable({data,columns,getCoreRowModel: getCoreRowModel(),getFilteredRowModel: getFilteredRowModel(),getSortedRowModel: getSortedRowModel(),getPaginationRowModel: getPaginationRowModel(),// ...})
2.Global search filters every column at once
The search Input calls table.setGlobalFilter on every keystroke. TanStack Table matches that value against all columns' rendered text and getFilteredRowModel re-filters the in-memory rows — no debounce, no server round-trip.
<Inputtype="text"placeholder="Search..."onChange={(e) => table.setGlobalFilter(e.target.value)}className="filter-input w-64"/>
3.Per-column filters are just column.setFilterValue
DataTableFilter is a thin Select wrapper around the column API — it reads column.getFilterValue() and writes back with column.setFilterValue(). BasicTableUsage composes one per filterable column through the table's filters render-prop.
const filterValue = column?.getFilterValue() as string | undefined<SelectonValueChange={(val) => column?.setFilterValue(val)}value={filterValue}>{/* ...options */}</Select>
4.Clicking a header sorts in place
Each sortable TableHead wires its onClick to column.getToggleSortingHandler(). TanStack re-sorts the already-filtered rows via getSortedRowModel — sorting, filtering, and pagination all compose on the same in-memory row list.
<TableHeadonClick={sortHandler}role={canSort ? 'button' : undefined}className={canSort ? 'cursor-pointer select-none' : undefined}>{/* header content + sort icon */}</TableHead>
5.Page size is just table.setPageSize
The page-size Select calls table.setPageSize directly, which recalculates pageIndex for you so the current scroll position stays sane. getPaginationRowModel then slices the filtered/sorted rows into pages, all without leaving the browser.
<Selectvalue={pagination.pageSize.toString()}onValueChange={(val) => table.setPageSize(Number(val))}>{/* ...page size options */}</Select>