All components

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-104workerStagingOperations
DEP-207identityProductionPlatform
DEP-311catalog-webPreviewStorefront
DEP-089checkout-apiProductionPayments
1"use client";
2
3import * 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";
16
17type Deployment = {
18 id: string;
19 service: string;
20 environment: string;
21 owner: string;
22};
23
24// Row ids are intentionally out of alphabetical order so sorting one column
25// 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];
52
53const 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];
80
81function ColumnFilter({ column }: { column: Column<Deployment, unknown> }) {
82 return (
83 <Input
84 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}
92
93export function DataTableDefaultExample() {
94 const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
95 [],
96 );
97 const [sorting, setSorting] = React.useState<SortingState>([]);
98
99 // 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 });
112
113 return (
114 <DataTable
115 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
1"use client";
2
3import {
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";
14
15type Item = { name: string };
16const columns: ColumnDef<Item>[] = [{ accessorKey: "name", header: "Name" }];
17
18export function DataTableStatesExample() {
19 const table = useReactTable({
20 data: [],
21 columns,
22 getCoreRowModel: getCoreRowModel(),
23 });
24
25 return (
26 <div className="grid gap-6 lg:grid-cols-3">
27 <DataTable table={table} status="loading" sortableHeaders={false} />
28 <DataTable
29 table={table}
30 sortableHeaders={false}
31 emptyState={
32 <TableEmptyState>
33 <span>No projects yet.</span>
34 <Button type="button" size="sm">
35 Create project
36 </Button>
37 </TableEmptyState>
38 }
39 />
40 <DataTable
41 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 again
49 </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

NameTypeDefaultDescription
tableTable<TData>RequiredTanStack table instance; keep sorting, filtering, pagination, visibility, and selection controlled in the consumer.
renderColumnFilter(column) => ReactNodeundefinedRenders a filter control beneath each filterable column header.
sortableHeadersbooleantrueRenders accessible sort buttons for sortable leaf headers when the table instance is configured for client or manual sorting.
classNames.sortButtonstringundefinedCustomizes 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 / errorStateReactNodeBuilt-in stateReplaceable content for each non-data body state.
toolbar / footerReactNodeundefinedComposition slots outside the bordered table surface.