Data Table
A sortable, filterable data table powered by TanStack Table, wrapped in SilicaUI's tokened styling — an opt-in package so the core stays lean.
Real use · sortable, selectable, paginated team table
| Trend | Status | |||||
|---|---|---|---|---|---|---|
Ada Lovelaceada@silica.dev | Owner | Enterprise | $2,400 | Active | ||
Grace Hoppergrace@silica.dev | Admin | Pro | $990 | Active | ||
Alan Turingalan@silica.dev | Member | Pro | $720 | Away | ||
Katherine Johnsonkate@silica.dev | Member | Starter | $190 | Invited | ||
Edsger Dijkstraedsger@silica.dev | Admin | Enterprise | $3,100 | Active |
0 rows selected
Loading & empty states
| Trend | Status | ||||
|---|---|---|---|---|---|
Ada Lovelaceada@silica.dev | Owner | Enterprise | $2,400 | Active | |
Grace Hoppergrace@silica.dev | Admin | Pro | $990 | Active | |
Alan Turingalan@silica.dev | Member | Pro | $720 | Away |
Using Data Table
Silica ships one design system through three paths. They share a class vocabulary but are not interchangeable — pick the one that matches what you are building.
Plain HTML — CSS classes
The Tailwind v4 plugin. Works in any framework or none — Rails, Django, PHP, Go, a static file. This path ships no JavaScript, so a component that needs interaction is styled but inert.
/* in your CSS */
@import "tailwindcss";
@plugin "@wizeworks/silicaui";Root class data-table, with 17 modifiers:
.data-table.data-table-accent.data-table-empty.data-table-error.data-table-info.data-table-neutral.data-table-pager.data-table-pagination.data-table-primary.data-table-row-clickable.data-table-scroll.data-table-secondary.data-table-skeleton.data-table-sort.data-table-sort-icon.data-table-sticky.data-table-success.data-table-warning
React
Typed components over those same classes, with behavior and accessibility from Base UI.
npm i @wizeworks/silicaui-tableimport { DataTable } from "@wizeworks/silicaui-table";Props
| Prop | Type | Required | Description |
|---|---|---|---|
| data | TData[] | yes | Row data. |
| columns | DataTableColumn<TData>[] | yes | Column definitions (accessorKey / header / cell, TanStack shape). |
| sortable | boolean | no | Column sorting. Default true. |
| selectable | boolean | no | Row-selection checkboxes (adds a leading column). Default false. |
| pagination | boolean | number | no | Client-side pagination: a number sets the page size, true uses 10, false shows everything. Default false. |
| zebra | boolean | no | Zebra striping. Default false. |
| hover | boolean | no | Row hover highlight. Default true. |
| stickyHeader | boolean | no | Sticky header while the body scrolls. Default false. |
| size | SilicaSize | no | Cell density (maps to .table-<size>). Default "md". |
| color | SilicaColor | no | Accent color for sort hover + selected-row tint. |
| emptyState | React.ReactNode | no | Rendered in place of the body when there are zero rows. |
| loading | boolean | no | Show placeholder skeleton rows instead of data. |
| loadingRows | number | no | Number of skeleton rows to show while loading. Default 5. |
| onRowClick | (row: TData) => void | no | Fired when a body row is clicked (whole-row affordance). |
| onSelectionChange | (rows: TData[]) => void | no | Fired with the selected originals whenever selection changes. |
| getRowId | (row: TData, index: number) => string | no | What makes a row THAT row, across a data change. Selection is remembered by this key. |
| className | string | no | — |
Source of the demo above
The exact file that renders the examples above, so it can never drift from them. It imports a few of this site’s own layout helpers (../lib/…), so read it for the component calls rather than copying it whole.
Show source
import { useState } from "react";
import { Badge } from "@wizeworks/silicaui-react";
import type { SilicaColor } from "@wizeworks/silicaui-react";
import { DataTable } from "@wizeworks/silicaui-table";
import type { DataTableColumn } from "@wizeworks/silicaui-table";
import { Sparkline } from "@wizeworks/silicaui-charts";
import { Section } from "../lib/Section";
interface TeamRow {
name: string;
email: string;
role: string;
plan: string;
mrr: number;
trend: number[];
status: { label: string; color: SilicaColor };
}
const TEAM: TeamRow[] = [
{ name: "Ada Lovelace", email: "ada@silica.dev", role: "Owner", plan: "Enterprise", mrr: 2400, trend: [12, 18, 15, 22, 28, 26, 31], status: { label: "Active", color: "success" } },
{ name: "Grace Hopper", email: "grace@silica.dev", role: "Admin", plan: "Pro", mrr: 990, trend: [8, 9, 11, 10, 13, 15, 14], status: { label: "Active", color: "success" } },
{ name: "Alan Turing", email: "alan@silica.dev", role: "Member", plan: "Pro", mrr: 720, trend: [5, 6, 6, 8, 7, 9, 12], status: { label: "Away", color: "warning" } },
{ name: "Katherine Johnson", email: "kate@silica.dev", role: "Member", plan: "Starter", mrr: 190, trend: [2, 3, 3, 4, 5, 5, 6], status: { label: "Invited", color: "neutral" } },
{ name: "Edsger Dijkstra", email: "edsger@silica.dev", role: "Admin", plan: "Enterprise", mrr: 3100, trend: [20, 22, 25, 24, 29, 33, 38], status: { label: "Active", color: "success" } },
{ name: "Barbara Liskov", email: "barbara@silica.dev", role: "Member", plan: "Pro", mrr: 880, trend: [9, 10, 12, 11, 14, 13, 16], status: { label: "Active", color: "success" } },
{ name: "Donald Knuth", email: "don@silica.dev", role: "Member", plan: "Starter", mrr: 140, trend: [1, 2, 2, 3, 3, 4, 4], status: { label: "Suspended", color: "error" } },
{ name: "Margaret Hamilton", email: "maggie@silica.dev", role: "Admin", plan: "Enterprise", mrr: 2750, trend: [18, 19, 21, 26, 30, 29, 34], status: { label: "Active", color: "success" } },
];
const COLUMNS: DataTableColumn<TeamRow>[] = [
{
accessorKey: "name",
header: "Member",
cell: ({ row }) => (
<div className="flex flex-col">
<span className="font-medium">{row.original.name}</span>
<span className="text-xs opacity-60">{row.original.email}</span>
</div>
),
},
{ accessorKey: "role", header: "Role" },
{
accessorKey: "plan",
header: "Plan",
cell: ({ row }) => <Badge>{row.original.plan}</Badge>,
},
{
accessorKey: "mrr",
header: "MRR",
cell: ({ row }) => (
<span className="tabular-nums">${row.original.mrr.toLocaleString()}</span>
),
},
{
id: "trend",
header: "Trend",
enableSorting: false,
cell: ({ row }) => (
<Sparkline
data={row.original.trend}
area
style={{ width: "5rem", height: "1.75rem" }}
/>
),
},
{
accessorKey: "status",
header: "Status",
enableSorting: false,
cell: ({ row }) => (
<Badge color={row.original.status.color}>{row.original.status.label}</Badge>
),
},
];
export function DataTableDemo() {
const [selected, setSelected] = useState<TeamRow[]>([]);
const [loading, setLoading] = useState(false);
return (
<>
<Section title="Real use · sortable, selectable, paginated team table">
<div className="flex flex-col gap-2">
<DataTable
data={TEAM}
columns={COLUMNS}
sortable
selectable
pagination={5}
zebra
color="primary"
onSelectionChange={setSelected}
/>
<p className="text-xs opacity-60">
{selected.length} row{selected.length === 1 ? "" : "s"} selected
</p>
</div>
</Section>
<Section title="Loading & empty states">
<div className="flex flex-col gap-3">
<button
className="w-fit text-xs underline opacity-70"
onClick={() => {
setLoading(true);
setTimeout(() => setLoading(false), 1500);
}}
>
Simulate loading
</button>
<DataTable
data={loading ? [] : TEAM.slice(0, 3)}
columns={COLUMNS}
loading={loading}
loadingRows={3}
emptyState={<span className="opacity-60">No members found.</span>}
/>
</div>
</Section>
</>
);
}The full API for Data Table, on every path, is available through the Silica MCP server.