DataTable
OrganismUnified table with `mode="static" | "paginated" | "server"`. Multi-column sort (Shift+click), global search, per-column filter (text + select), pagination, unified loading/empty/error state, and `rowActions` (a per-row actions menu with id, href, hidden, disabledReason, destructive, group and order).
| Name | Role | Status | Joined | |
|---|---|---|---|---|
| Alice Martin | alice@example.com | Admin | Active | 2024-01-15 |
| Bob Johnson | bob@example.com | Member | Active | 2024-02-20 |
| Carol Williams | carol@example.com | Editor | Inactive | 2024-03-10 |
| David Brown | david@example.com | Member | Active | 2024-04-05 |
| Eve Davis | eve@example.com | Admin | Active | 2024-05-18 |
Showing 1–5 of 8
<DataTable
caption="Users"
searchPlaceholder="Search users…"
pageSize={5}
rows={users}
columns={[
{ key: 'name', header: 'Name' },
{ key: 'email', header: 'Email' },
{ key: 'role', header: 'Role' },
{ key: 'status', header: 'Status', render: (r) => <Badge variant={...}>{r.status}</Badge> },
{ key: 'joined', header: 'Joined' },
]}
/>| Product | Category | Price | Stock |
|---|---|---|---|
| Widget A | Tools | 29.99 | 150 |
| Gadget B | Electronics | 99.00 | 42 |
| Part C | Tools | 9.50 | 500 |
| Device D | Electronics | 249.00 | 18 |
| Item E | Misc | 14.75 | 200 |
Showing 1–5 of 5
<DataTable
rows={products}
columns={[
{ key: 'name', header: 'Product', sortable: true },
{ key: 'price', header: 'Price', sortable: true, align: 'right' },
]}
/>| Name | Team | Joined | |
|---|---|---|---|
| Loading…Loading… | |||
No results
// Unified server-side data flow — the component owns sort/filter/pagination state
// and calls `fetchPage` whenever it changes.
<DataTable<User>
mode="server"
fetchPage={async ({ page, pageSize, sort, search, filters }) => {
const res = await fetch(buildUrl({ page, pageSize, sort, search, filters }));
const { rows, total } = await res.json();
return { rows, total };
}}
pageSize={5}
searchPlaceholder="Search users…"
columns={[
{ key: 'name', header: 'Name', sortable: true },
{ key: 'email', header: 'Email', sortable: true, filter: { kind: 'text' } },
{ key: 'team', header: 'Team', sortable: true, filter: { kind: 'select', options: [
{ label: 'Platform', value: 'platform' },
{ label: 'Growth', value: 'growth' },
] } },
]}
/>| Company | Status | Actions | |
|---|---|---|---|
| Northwind Traders | buy@northwind.example | Active | |
| Contoso Ltd | — | Active | |
| Fabrikam | info@fabrikam.example | Archived |
Nothing run yet
// A trailing column with one menu per row. Every per-row field takes a function of the row.
<DataTable<Company>
rows={companies}
columns={columns}
rowActionsLabel={(row) => `Actions for ${row.name}`}
rowActions={[
{ id: 'open', label: 'Open', href: (row) => `/companies/${row.id}` },
{ id: 'call', label: 'Call', group: 'contact', order: 10, onClick: (row) => call(row) },
{ id: 'sign', label: 'Send for signature', group: 'contact', order: 20,
disabled: (row) => !row.email, disabledReason: (row) => (row.email ? undefined : 'Add an email address first') },
{ id: 'restore', label: 'Restore', hidden: (row) => row.status !== 'Archived' },
{ id: 'delete', label: 'Delete', group: 'danger', order: 1000, destructive: true, onClick: remove },
]}
/>