Data Table
ProInstallation
npx shadcn add @useplanes/data-table
Works once the @useplanes registry is set up in your project (one-time). After that, copy and run commands directly.
Unlock with Planes Pro
$99 once · 110 Pro components · lifetime updates.
const SHORT_DATE = new Intl.DateTimeFormat("en-US", { month: "short", day: "2-digit" });
<DataTable
columns={[
{ key: "id", header: "Invoice", sortable: true, mono: true, width: 120 },
{ key: "customer", header: "Customer", sortable: true },
{ key: "status", header: "Status", sortable: true, filterable: true },
{ key: "issued", header: "Issued", sortable: true, searchValue: (r) => SHORT_DATE.format(r.issued), cell: (r) => SHORT_DATE.format(r.issued) },
{ key: "amount", header: "Amount", sortable: true, align: "right", cell: (r) => money(r.amount) },
]}
data={invoices}
getRowId={(r) => r.id}
selectable
defaultSort={[{ key: "issued", direction: "desc" }]}
onRowClick={(r) => router.push(`/invoices/${r.id}`)}
actions={({ ids, clear }) => (
<DataTableAction onClick={() => { exportRows(ids); clear(); }}>Export</DataTableAction>
)}
/>Examples
Ten thousand rows, windowed
Turn on virtual and the table scrolls every row in a fixed-height viewport, rendering only what is on screen.
<DataTable columns={columns} data={rows} getRowId={(r) => r.id} virtual height={520} rowLabel="events" />Controlled sort synced to the URL
Hold the sort in your own state so it survives navigation.
const [sort, setSort] = useState<DataTableSort[]>([{ key: "issued", direction: "desc" }]);
<DataTable columns={columns} data={rows} getRowId={(r) => r.id} sort={sort} onSortChange={setSort} />Use cases
Admin dashboards
Invoices, orders, users: dense rows with the sort, filter and bulk actions people expect from a back office.
Deploy and job logs
Virtualized mode scrolls thousands of runs with a sticky header and keyboard navigation that never drops a frame.
Approval queues
Shift-click a range, watch the count roll up, approve in one action from the bar.
Mobile-first SaaS
The same table becomes stacked cards under 640px instead of a squeezed horizontal scroll.