Table
Table
A virtualized data table with typed columns, selection, and column controls.
Sort a column, drag a resize handle, or select cells and copy them. Use the column settings to hide, reorder, and pin columns. The 10,000-row option uses the same component and API.
View source
"use client";
import { useMemo, useState } from "react";
import {
DataTable,
defineColumns,
format,
TableActionBar,
useDataTable,
} from "@mendylanda/ui/table";
import { Button } from "@mendylanda/ui/primitives/button";
type Project = {
id: string;
name: string;
owner: { name: string };
budget: number;
updatedAt: Date;
};
const columns = defineColumns<Project>((column) => [
column.accessor("name", { label: "Project", pin: "start", size: 250 }),
column.accessor("owner.name", { label: "Owner" }),
column.accessor("budget", { label: "Budget", format: format.currency("USD") }),
column.accessor("updatedAt", {
label: "Updated",
format: format.date({ dateStyle: "medium" }, "en-US"),
}),
]);
export function TableDemo() {
const [large, setLarge] = useState(false);
const rows = useMemo(
() =>
Array.from({ length: large ? 10000 : 80 }, (_, index) => ({
id: `project-${index}`,
name: `Project ${index + 1}`,
owner: { name: ["Alex Morgan", "Jordan Lee", "Casey Taylor"][index % 3] },
budget: 1200 + index * 37,
updatedAt: new Date(2026, 0, (index % 28) + 1),
})),
[large],
);
const table = useDataTable({
rows,
columns,
getRowId: (row) => row.id,
enableRowSelection: true,
preferences: { key: "projects", scope: "docs-demo" },
});
return (
<DataTable
table={table}
label="Projects"
height={420}
toolbar={
<Button size="sm" variant="outline" onClick={() => setLarge(!large)}>
{large ? "Show 80 rows" : "Try 10,000 rows"}
</Button>
}
footer={
<TableActionBar
count={Object.keys(table.state.rowSelection).length}
onClear={() => table.resetRowSelection(true)}
>
<span className="text-sm text-muted-foreground">
Select cells and press ⌘C or Ctrl+C to copy.
</span>
</TableActionBar>
}
/>
);
}
Start with rows and columns
After installation, this is a complete table:
"use client";
import { DataTable, defineColumns, format } from "@mendylanda/ui/table";
type Project = { id: string; name: string; budget: number };
const rows: Project[] = [
{ id: "website", name: "Website", budget: 2400 },
{ id: "mobile", name: "Mobile app", budget: 5600 },
];
const columns = defineColumns<Project>((column) => [
column.accessor("name", { label: "Project", pin: "start" }),
column.accessor("budget", { label: "Budget", format: format.currency("USD", "en-US") }),
]);
export function ProjectsTable() {
return <DataTable rows={rows} columns={columns} getRowId={(row) => row.id} label="Projects" />;
}Use an ID that identifies the record across sorting, filtering, and page loads. An array index is not a stable record identity.
What comes by default
| Behavior | Default |
|---|---|
| Rendering | Virtual rows and columns, sticky header, 44px rows. |
| Columns | Resizing, settings, visibility, reordering, pinning, and spare-width sharing. |
| Data processing | Client sorting and filtering; pagination off. |
| Cell selection | Drag and keyboard selection, with spreadsheet-style copying. |
| Row selection | Opt in with enableRowSelection; checkboxes appear inside the first visible column. |
| Height | Fit content, capped at 65dvh. Use layout="fill" for a full-page table. |
| Appearance | Your application's semantic colors, font, and radius. |
Filters, pagination controls, row actions, and saved preferences are configured when needed. They are not required to render a table.
Connect your application
Use useDataTable when the query or other controls need access to state. Pass the resulting instance to <DataTable table={table} />. For a custom arrangement, compose TableView, TableFilters, TableColumnSettings, and other controls around that instance.
- Columns and rendering: typed accessors, computed values, custom cells, and formatters.
- Layout and sizing: embedded tables, full-page tables, growing columns, and expanded rows.
- Data and queries: loading, errors, filters, server sorting, and incremental loading.
- Selection and clipboard: cells, rows, all matching results, and export.
- Preferences and saved views: persistence and application-owned views.
- Performance: what virtualization covers and where application work remains.
The API reference lists the props and exported functions.