Data Table
alphav0.1.2A typed, composable table primitive with column accessors and per-cell rendering.
Context
DataTable is the foundational data display component — every more advanced table (sortable, paginated, virtualized) in this registry is expected to compose on top of it. It is deliberately small: one render, no client state, no DOM-level magic.
Installation
pnpm dlx shadcn@latest initpnpm dlx shadcn@latest add @ilinxa/data-tableAdd -fixtures for dummy data:
pnpm dlx shadcn@latest add @ilinxa/data-table-fixturesCLI can't resolve @ilinxa? The namespace is listed in the official shadcn registry directory, so current CLIs need no configuration. If yours can't resolve it (older or pinned versions, self-hosted mirrors), register it manually in components.json:
"registries": {
"@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}Preview
| Name | Role | Status | Last Seen |
|---|---|---|---|
Aria Montgomeryaria@ilinxa.dev | Owner | Active | 2 minutes ago |
Bilal Hashemibilal@ilinxa.dev | Admin | Active | 1 hour ago |
Camille Okaforcamille@ilinxa.dev | Member | Invited | — |
Dimitri Volkovdimitri@ilinxa.dev | Member | Active | Yesterday |
Esme Tanakaesme@ilinxa.dev | Viewer | Suspended | 3 weeks ago |
Demo source
import { Badge } from "@/components/ui/badge";import { DataTable } from "./data-table";import { DEMO_USERS, type DemoUser } from "./dummy-data";import type { DataTableColumn } from "./types"; const columns: DataTableColumn<DemoUser>[] = [ { id: "name", header: "Name", accessor: (row) => ( <div className="flex flex-col"> <span className="font-medium text-foreground">{row.name}</span> <span className="text-xs text-muted-foreground">{row.email}</span> </div> ), }, { id: "role", header: "Role", accessor: (row) => <span className="text-foreground">{row.role}</span>, }, { id: "status", header: "Status", accessor: (row) => { const variant = row.status === "Active" ? "default" : row.status === "Invited" ? "secondary" : "destructive"; return <Badge variant={variant}>{row.status}</Badge>; }, }, { id: "lastSeen", header: "Last Seen", align: "right", accessor: (row) => ( <span className="text-sm text-muted-foreground">{row.lastSeen}</span> ), },]; export default function DataTableDemo() { return ( <DataTable columns={columns} rows={DEMO_USERS} rowKey={(row) => row.id} caption="A starter table rendered from dummy data." /> );} Usage
When to use
Reach for DataTable when you need to render a list of records with consistent column structure. It is intentionally unopinionated about sorting, filtering, and pagination — those concerns compose on top via the column accessor and parent state.
Basic example
import { DataTable, type DataTableColumn } from "@/components/data-table"
type User = { id: string; name: string; email: string }
const columns: DataTableColumn<User>[] = [
{ id: "name", header: "Name", accessor: (r) => r.name },
{ id: "email", header: "Email", accessor: (r) => r.email },
]
export function Example({ users }: { users: User[] }) {
return (
<DataTable
columns={columns}
rows={users}
rowKey={(r) => r.id}
/>
)
}Notes
accessorcan return anyReactNode, so composing badges, avatars, or actions per cell is the intended path.- Column
alignapplies to both header and cells; usewidthfor fixed-width columns (e.g. action menus). - Pass an
emptyStatenode to override the default empty message.
Features
- Generic over row type — fully type-safe column accessors
- Per-column alignment and fixed widths
- Custom empty state slot
- Composes any ReactNode in cells (badges, avatars, actions)