ComponentsDataTable
DataTable
Display rows of data with sorting, filtering, selection, and pagination.
On this page
Use this component from the local workspace package. Follow the setup guide first. A public npm package and registry are not available yet.
View registry filesImport the component into your page or component file.
import {
DataTable,
type DataTableColumnDef,
} from "@dethink/components";Try the examples, then open the code to use them in your app. Headers are real buttons — sort with Enter or Space.
Sorting and filtering
Column defs with a custom status cell, default sort, and the global filter toolbar.
| api-gateway | production | success | 4m 12s |
| api-gateway | staging | success | 3m 58s |
| billing | production | failed | 1m 03s |
| web-app | staging | success | 6m 41s |
| worker | production | running | — |
Show sourceexamples/data-table/basic.tsx
"use client";
import { DataTable, type DataTableColumnDef } from "@dethink/components";
type Deploy = {
id: string;
service: string;
env: string;
status: "success" | "failed" | "running";
duration: string;
};
const deploys: Deploy[] = [
{
id: "d1",
service: "api-gateway",
env: "production",
status: "success",
duration: "4m 12s",
},
{
id: "d2",
service: "billing",
env: "production",
status: "failed",
duration: "1m 03s",
},
{
id: "d3",
service: "web-app",
env: "staging",
status: "success",
duration: "6m 41s",
},
{
id: "d4",
service: "worker",
env: "production",
status: "running",
duration: "—",
},
{
id: "d5",
service: "api-gateway",
env: "staging",
status: "success",
duration: "3m 58s",
},
];
const columns: DataTableColumnDef<Deploy>[] = [
{ accessorKey: "service", header: "Service" },
{ accessorKey: "env", header: "Environment" },
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => {
const status = row.original.status;
const tone =
status === "success"
? "text-success"
: status === "failed"
? "text-destructive"
: "text-info";
return <span className={`font-medium ${tone}`}>{status}</span>;
},
},
{ accessorKey: "duration", header: "Duration" },
];
export function DataTableBasic() {
return (
<DataTable
columns={columns}
data={deploys}
getRowId={(row) => row.id}
caption="Latest deployments"
enableGlobalFilter
globalFilterPlaceholder="Filter deployments…"
defaultSorting={[{ id: "service", desc: false }]}
/>
);
}Empty, loading, and error states
The three non-data states render inside the table region and announce to assistive tech.
| No projects yet. Create one to get started. |
Show sourceexamples/data-table/states.tsx
"use client";
import { useState } from "react";
import {
Button,
DataTable,
type DataTableColumnDef,
} from "@dethink/components";
type Row = { id: string; name: string };
const columns: DataTableColumnDef<Row>[] = [
{ accessorKey: "name", header: "Name" },
];
export function DataTableStates() {
const [state, setState] = useState<"loading" | "empty" | "error">("empty");
return (
<div className="space-y-3">
<div className="flex justify-center gap-2">
{(["empty", "loading", "error"] as const).map((mode) => (
<Button
key={mode}
size="sm"
variant={state === mode ? "solid" : "outline"}
onClick={() => setState(mode)}
>
{mode}
</Button>
))}
</div>
<DataTable
columns={columns}
data={[]}
getRowId={(row) => row.id}
loading={state === "loading"}
error={
state === "error"
? "Could not load projects — retry shortly."
: undefined
}
emptyContent="No projects yet. Create one to get started."
/>
</div>
);
}Examples that combine components for common tasks.
Incident dashboard with bulk actions
Sorting, filtering, multi-select, and pagination working together: select incidents across the filtered set, acknowledge them in bulk, and a live region confirms the change.
0 selected
| Elevated 5xx on checkout | billing | sev1 | open | |
| Slow queries on search | search | sev2 | open | |
| Cache hit rate dropped | api-gateway | sev2 | open | |
| Webhook retries spiking | integrations | sev3 | acknowledged |
Show sourceexamples/data-table/recipe-ops-dashboard.tsx
"use client";
import { useState } from "react";
import {
Button,
DataTable,
type DataTableColumnDef,
type DataTableRowSelectionState,
} from "@dethink/components";
type Incident = {
id: string;
title: string;
service: string;
severity: "sev1" | "sev2" | "sev3";
status: "open" | "acknowledged" | "resolved";
};
const initialIncidents: Incident[] = [
{
id: "i1",
title: "Elevated 5xx on checkout",
service: "billing",
severity: "sev1",
status: "open",
},
{
id: "i2",
title: "Slow queries on search",
service: "search",
severity: "sev2",
status: "open",
},
{
id: "i3",
title: "Webhook retries spiking",
service: "integrations",
severity: "sev3",
status: "acknowledged",
},
{
id: "i4",
title: "Cache hit rate dropped",
service: "api-gateway",
severity: "sev2",
status: "open",
},
{
id: "i5",
title: "Cert expiring in 7 days",
service: "edge",
severity: "sev3",
status: "open",
},
];
const severityTone: Record<Incident["severity"], string> = {
sev1: "bg-destructive/10 text-destructive",
sev2: "bg-warning/15 text-warning",
sev3: "bg-muted text-muted-foreground",
};
const columns: DataTableColumnDef<Incident>[] = [
{ accessorKey: "title", header: "Incident" },
{ accessorKey: "service", header: "Service" },
{
accessorKey: "severity",
header: "Severity",
cell: ({ row }) => (
<span
className={`rounded-full px-2 py-0.5 text-xs font-medium uppercase ${severityTone[row.original.severity]}`}
>
{row.original.severity}
</span>
),
},
{ accessorKey: "status", header: "Status" },
];
/**
* The full feature set in one seat: sorting, filtering, selection, and
* pagination together, with a bulk action operating on the selected rows
* and a live region confirming what changed.
*/
export function DataTableRecipeOpsDashboard() {
const [incidents, setIncidents] = useState(initialIncidents);
const [rowSelection, setRowSelection] = useState<DataTableRowSelectionState>(
{},
);
const [statusMessage, setStatusMessage] = useState("");
const selectedIds = Object.keys(rowSelection).filter(
(id) => rowSelection[id],
);
return (
<div className="space-y-3">
<div className="flex items-center justify-between gap-3">
<p aria-live="polite" className="text-muted-foreground text-sm">
{statusMessage || `${selectedIds.length} selected`}
</p>
<Button
size="sm"
disabled={selectedIds.length === 0}
onClick={() => {
setIncidents((current) =>
current.map((incident) =>
selectedIds.includes(incident.id)
? { ...incident, status: "acknowledged" }
: incident,
),
);
setStatusMessage(`Acknowledged ${selectedIds.length} incident(s).`);
setRowSelection({});
}}
>
Acknowledge selected
</Button>
</div>
<DataTable
columns={columns}
data={incidents}
getRowId={(row) => row.id}
selectionMode="multiple"
rowSelection={rowSelection}
onRowSelectionChange={setRowSelection}
enableGlobalFilter
globalFilterPlaceholder="Filter incidents…"
enablePagination
defaultPagination={{ pageIndex: 0, pageSize: 4 }}
pageSizeOptions={[4, 8]}
defaultSorting={[{ id: "severity", desc: false }]}
/>
</div>
);
}Every stateful feature is controllable (value + onChange) or uncontrolled (default value), and manual modes hand the work to your server.
| Prop | What it does | Default |
|---|---|---|
columnsDataTableColumnDef<TData>[] | TanStack-style column definitions: accessorKey, header, and an optional cell renderer. | Not set |
dataTData[] | Row data; pair with getRowId for stable selection keys. | Not set |
enableSortingboolean | Sortable headers with accessible sort buttons; control with sorting/onSortingChange or seed defaultSorting. | true |
enableGlobalFilterboolean | Toolbar text filter across all columns; globalFilterPlaceholder labels it. | false |
enableColumnFilters / renderColumnFilterboolean / (column) => ReactNode | Per-column filtering with a custom filter UI per column. | false / — |
selectionMode"none" | "single" | "multiple" | Row selection with header select-all in multiple mode; control with rowSelection/onRowSelectionChange. | "none" |
enablePaginationboolean | Pagination footer with pageSizeOptions; control with pagination/onPaginationChange. | false |
loading / loadingContentboolean / ReactNode | Loading state announced to assistive tech. | false / built-in |
error / emptyContentReactNode | Error and empty states rendered inside the table region. | Not set |
manualSorting / manualFiltering / manualPaginationboolean | Server-driven mode: the table emits state changes and renders what you pass, with pageCount/rowCount. | false |
renderRowActions(row) => ReactNode | Trailing actions cell per row — menus, buttons, links. | Not set |
density / caption / labelsdensity scale / ReactNode / DataTableLabels | Visual density, an accessible caption, and overridable UI strings for localization. | Not set |