Data Table
Enterprise-grade data table with sorting, filtering, pagination, and row actions
The Data Table component provides comprehensive table functionality inspired by Airtable, including multi-column sorting, search, pagination, row selection, and export capabilities.
Basic Usage
Simple Table
With All Features
Full Featured Table
Schema
interface TableColumn {
header: string; // Column header text
accessorKey: string; // Data property key
width?: string; // Column width (e.g., '100px', '20%')
sortable?: boolean; // Enable sorting for this column
}
interface DataTableSchema {
type: 'data-table';
// Structure
columns: TableColumn[]; // Column definitions
data: Record<string, any>[]; // Table data
caption?: string; // Table caption
// Features
pagination?: boolean; // Enable pagination (default: true)
pageSize?: number; // Items per page (default: 10)
searchable?: boolean; // Enable search (default: true)
selectable?: boolean; // Enable row selection (default: false)
sortable?: boolean; // Enable sorting (default: true)
exportable?: boolean; // Enable CSV export (default: false)
rowActions?: boolean; // Show edit/delete buttons (default: false)
// Advanced features
resizableColumns?: boolean; // Allow column resizing (default: true)
reorderableColumns?: boolean; // Allow column reordering (default: true)
// Inline editing
editable?: boolean; // Enable inline cell editing (default: false)
singleClickEdit?: boolean; // Enter edit mode on single click (default: false)
renderCellEditor?: (ctx: { // Host-supplied editor widget; null -> built-in input
column: any;
row: any; // the persisted record
pendingRow: any; // row + this row's staged, unsaved edits (#7188)
value: any;
stage: (v: any) => void;
commit: (v?: any) => void;
cancel: () => void;
}) => ReactNode;
// Styling
className?: string; // Tailwind CSS classes on the table wrapper
cellClassName?: string; // Tailwind CSS classes on the utility cells only
// (select / row number / row actions)
// Base properties
id?: string;
visible?: boolean | string | { dialect?: string; source: string };
testId?: string;
}Cell styling
className styles the table wrapper. Body cells have two class slots, and they
reach disjoint cells — neither is a superset of the other, and no cell gets both:
TableColumn.cellClassName— the data cells of that one column.DataTableSchema.cellClassName— the table's utility cells only: the leading selection-checkbox cell (selectable), the row-number cell (showRowNumbers), and the trailing row-actions cell (rowActions). It never reaches a data cell.
(The empty-state cell and the add-record row take neither.)
Row density is therefore a pair of settings, not one. Per-cell padding is where
row height has to be expressed — height is a property of the cells, not of the row
element, so rowClassName cannot express it — so a compact table sets the same
density class on every column and on the schema, the second so the checkbox and
row-number cells stay the same height as the data beside them. That is exactly what
object-grid does for its rowHeight modes:
{
"type": "data-table",
"selectable": true,
"showRowNumbers": true,
"cellClassName": "px-2 py-1 text-sm",
"columns": [
{ "header": "Name", "accessorKey": "name", "cellClassName": "px-2 py-1 text-sm" },
{ "header": "Amount", "accessorKey": "amount", "cellClassName": "px-2 py-1 text-sm text-right" }
],
"data": [
{ "name": "Ada Lovelace", "amount": 120 },
{ "name": "Grace Hopper", "amount": 340 }
]
}Drop the per-column half and the data cells stay at the table primitive's default
p-4 — the schema-level key alone does not compact a row.
Inline editing
With editable: true a cell enters edit mode on double-click (or on single click
with singleClickEdit: true) and the table renders one of its built-in editors —
text, number, date — chosen from the column's type.
renderCellEditor lets the host supply a widget instead. The table calls it first
for every cell it is about to edit; return a node to use it, or null to fall
through to the built-in editor for that column. This is how object-grid gives a
select or lookup cell the same dedicated control the form uses, without the
component layer having to re-implement it.
The returned node is wrapped by the table so it inherits the exit-edit
affordances the built-in editors have: Enter commits from a single-line input,
Escape cancels, and a click outside commits. Use stage to record a value while
staying in edit mode, commit to save, and cancel to discard.
The context carries the row twice, on purpose: row is the persisted record, and
pendingRow is that record with the row's staged, unsaved edits merged over it
(the same object as row when nothing is staged). A widget that scopes itself by
a sibling field — a dependsOn lookup — should read pendingRow, so a parent
edited in the same row re-scopes the child before anything is saved; row stays
the place to read what the data source last returned.
Examples
Product Inventory
Inventory Management
User Management
User Table
Sales Report
Sales Data
Features
The Data Table component provides:
- Sorting: Click column headers to sort (ascending/descending/none)
- Search: Real-time search across all columns
- Pagination: Navigate through large datasets
- Selection: Select multiple rows with checkboxes
- Export: Download table data as CSV
- Row Actions: Built-in edit and delete buttons
- Resizable Columns: Drag column borders to resize
- Column Reordering: Drag columns to reorder