Data Table
Composable TanStack Table renderer with controlled sorting, per-column filters, and replaceable table states.
Sorting and per-column filters
Controlled TanStack sorting and filter state with an independent input for every filterable column.
| ID | |||
|---|---|---|---|
| DEP-104 | worker | Staging | Operations |
| DEP-207 | identity | Production | Platform |
| DEP-311 | catalog-web | Preview | Storefront |
| DEP-089 | checkout-api | Production | Payments |
default-example.tsx
1"use client";23import * as React from "react";4import {5 getCoreRowModel,6 getFilteredRowModel,7 getSortedRowModel,8 useReactTable,9 type Column,10 type ColumnDef,11 type ColumnFiltersState,12 type SortingState,13} from "@tanstack/react-table";14import { Input } from "@/components/ui/input";15import { DataTable } from "@/registry/ui/data-table";1617type Deployment = {18 id: string;19 service: string;20 environment: string;21 owner: string;22};2324// Row ids are intentionally out of alphabetical order so sorting one column25// does not make unrelated columns look alphabetized.26const deployments: Deployment[] = [27 {28 id: "DEP-104",29 service: "worker",30 environment: "Staging",31 owner: "Operations",32 },33 {34 id: "DEP-207",35 service: "identity",36 environment: "Production",37 owner: "Platform",38 },39 {40 id: "DEP-311",41 service: "catalog-web",42 environment: "Preview",43 owner: "Storefront",44 },45 {46 id: "DEP-089",47 service: "checkout-api",48 environment: "Production",49 owner: "Payments",50 },51];5253const columns: ColumnDef<Deployment>[] = [54 {55 id: "id",56 accessorKey: "id",57 header: "ID",58 sortDescFirst: false,59 enableSorting: false,60 },61 {62 id: "service",63 accessorKey: "service",64 header: "Service",65 sortDescFirst: false,66 },67 {68 id: "environment",69 accessorKey: "environment",70 header: "Environment",71 sortDescFirst: false,72 },73 {74 id: "owner",75 accessorKey: "owner",76 header: "Owner",77 sortDescFirst: false,78 },79];8081function ColumnFilter({ column }: { column: Column<Deployment, unknown> }) {82 return (83 <Input84 value={(column.getFilterValue() as string | undefined) ?? ""}85 onChange={(event) => column.setFilterValue(event.target.value)}86 placeholder={`Filter ${column.id}`}87 aria-label={`Filter ${column.id}`}88 className="h-8 min-w-28"89 />90 );91}9293export function DataTableDefaultExample() {94 const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(95 [],96 );97 const [sorting, setSorting] = React.useState<SortingState>([]);9899 // Sorting reorders rows by one column at a time; other cells move with their row.100 const table = useReactTable({101 data: deployments,102 columns,103 state: { columnFilters, sorting },104 onColumnFiltersChange: setColumnFilters,105 onSortingChange: setSorting,106 getCoreRowModel: getCoreRowModel(),107 getFilteredRowModel: getFilteredRowModel(),108 getSortedRowModel: getSortedRowModel(),109 enableMultiSort: false,110 isMultiSortEvent: () => false,111 });112113 return (114 <DataTable115 table={table}116 renderColumnFilter={(column) => <ColumnFilter column={column} />}117 emptyState="No deployments match those filters."118 />119 );120}
Table states
Replaceable loading, empty, and error content without coupling fetch state to the table.
| Name |
|---|
| Name |
|---|
No projects yet. |
| Name |
|---|
Projects could not be loaded. |
states-example.tsx
1"use client";23import {4 getCoreRowModel,5 useReactTable,6 type ColumnDef,7} from "@tanstack/react-table";8import { Button } from "@/components/ui/button";9import {10 DataTable,11 TableEmptyState,12 TableErrorState,13} from "@/registry/ui/data-table";1415type Item = { name: string };16const columns: ColumnDef<Item>[] = [{ accessorKey: "name", header: "Name" }];1718export function DataTableStatesExample() {19 const table = useReactTable({20 data: [],21 columns,22 getCoreRowModel: getCoreRowModel(),23 });2425 return (26 <div className="grid gap-6 lg:grid-cols-3">27 <DataTable table={table} status="loading" sortableHeaders={false} />28 <DataTable29 table={table}30 sortableHeaders={false}31 emptyState={32 <TableEmptyState>33 <span>No projects yet.</span>34 <Button type="button" size="sm">35 Create project36 </Button>37 </TableEmptyState>38 }39 />40 <DataTable41 table={table}42 status="error"43 sortableHeaders={false}44 errorState={45 <TableErrorState>46 <span>Projects could not be loaded.</span>47 <Button type="button" variant="outline" size="sm">48 Try again49 </Button>50 </TableErrorState>51 }52 />53 </div>54 );55}
Installation & source
Install via the shadcn CLI or copy the registry files manually.
bash
npx shadcn@latest add @tt-ui/data-table
Props
| Name | Type | Default | Description |
|---|---|---|---|
| table | Table<TData> | Required | TanStack table instance; keep sorting, filtering, pagination, visibility, and selection controlled in the consumer. |
| renderColumnFilter | (column) => ReactNode | undefined | Renders a filter control beneath each filterable column header. |
| sortableHeaders | boolean | true | Renders accessible sort buttons for sortable leaf headers when the table instance is configured for client or manual sorting. |
| classNames.sortButton | string | undefined | Customizes the inner sortable header button without replacing the table renderer. |
| status | "ready" | "loading" | "error" | "ready" | Selects the body state without changing table state. |
| emptyState / loadingState / errorState | ReactNode | Built-in state | Replaceable content for each non-data body state. |
| toolbar / footer | ReactNode | undefined | Composition slots outside the bordered table surface. |