DataTable Experimental
Rendered table with sorting, row selection, column resizing, and optional virtual scrolling for large datasets.
import { DataTable } from 'phoundry-ui'; Basic Table
| Name | Role | Status | |
|---|---|---|---|
| Alice Chen | [email protected] | Engineer | Active |
| Bob Rivera | [email protected] | Designer | Away |
| Carol Kim | [email protected] | Manager | Active |
| Dan Okafor | [email protected] | Engineer | Offline |
Show code
const people = [
{ name: 'Alice Chen', email: '[email protected]', role: 'Engineer', status: 'Active' },
{ name: 'Bob Rivera', email: '[email protected]', role: 'Designer', status: 'Away' },
{ name: 'Carol Kim', email: '[email protected]', role: 'Manager', status: 'Active' },
];
const columns = [
{ id: 'name', header: 'Name', accessorKey: 'name' },
{ id: 'email', header: 'Email', accessorKey: 'email' },
{ id: 'role', header: 'Role', accessorKey: 'role' },
{ id: 'status', header: 'Status', accessorKey: 'status' },
];
<DataTable data={people} columns={columns} striped />Sortable Columns
| Name | Role | Status | |
|---|---|---|---|
| Alice Chen | [email protected] | Engineer | Active |
| Bob Rivera | [email protected] | Designer | Away |
| Carol Kim | [email protected] | Manager | Active |
| Dan Okafor | [email protected] | Engineer | Offline |
Show code
const columns = [
{ id: 'name', header: 'Name', accessorKey: 'name', sortable: true },
{ id: 'email', header: 'Email', accessorKey: 'email', sortable: true },
{ id: 'role', header: 'Role', accessorKey: 'role', sortable: true },
{ id: 'status', header: 'Status', accessorKey: 'status' },
];
<DataTable data={people} columns={columns} />With Selection
| Name | Role | Status | ||
|---|---|---|---|---|
| Alice Chen | [email protected] | Engineer | Active | |
| Bob Rivera | [email protected] | Designer | Away | |
| Carol Kim | [email protected] | Manager | Active | |
| Dan Okafor | [email protected] | Engineer | Offline |
Selected: none
Show code
let selected = $state<string[]>([]);
<DataTable
data={people}
columns={columns}
selectable
onSelectionChange={(ids) => selected = ids}
/>Cell Editors
All cell types — text, status, select, multi-select, rating, boolean, number, date, URL. Click cells to edit. Changes are reflected in the data immediately.
| Title | Status | Priority | Tags | Rating | Done | Hours | Due Date | Link |
|---|---|---|---|---|---|---|---|---|
Build DataTable cell editors | 8 | 2026/06/13 | ||||||
Write documentation | 4 | Set date... | ||||||
Deploy staging environment | 2 | 2026/06/09 | ||||||
Design system review | 0 | 2026/06/17 | ||||||
API rate limiting | 6 | Set date... |
Show code
const statusOptions = [
{ id: 'todo', label: 'Todo', color: '#737373' },
{ id: 'in-progress', label: 'In Progress', color: '#3b82f6' },
];
const tagOptions = [
{ id: 'frontend', label: 'Frontend', color: '#3b82f6' },
{ id: 'backend', label: 'Backend', color: '#22c55e' },
];
const columns = [
{ id: 'title', header: 'Title', accessorKey: 'title',
cellType: 'text', cellConfig: { placeholder: 'Task title...' } },
{ id: 'status', header: 'Status', accessorKey: 'status',
cellType: 'status', cellConfig: { options: statusOptions } },
{ id: 'tags', header: 'Tags', accessorKey: 'tags',
cellType: 'multi-select', cellConfig: { options: tagOptions } },
{ id: 'rating', header: 'Rating', accessorKey: 'rating',
cellType: 'rating' },
{ id: 'done', header: 'Done', accessorKey: 'done',
cellType: 'boolean' },
{ id: 'dueDate', header: 'Due', accessorKey: 'dueDate',
cellType: 'date' },
{ id: 'website', header: 'Link', accessorKey: 'website',
cellType: 'url' },
];
<DataTable
data={items}
columns={columns}
onCellChange={handleCellChange}
/>Grouped rows (groupBy)
Rows are bucketed by the chosen column after the current sort. Group headers reuse built-in cell display in read-only mode.
| Title | Status | Priority | Tags | Rating | Done | Hours | Due Date | Link |
|---|---|---|---|---|---|---|---|---|
Done | ||||||||
Deploy staging environment | 2 | 2026/06/09 | ||||||
In Progress | ||||||||
Build DataTable cell editors | 8 | 2026/06/13 | ||||||
Review | ||||||||
Design system review | 0 | 2026/06/17 | ||||||
Todo | ||||||||
Write documentation | 4 | Set date... | ||||||
API rate limiting | 6 | Set date... | ||||||
Show code
<DataTable
data={taskData}
columns={taskColumns}
getRowId={(row) => row.id}
groupBy="status"
onCellChange={handleCellChange}
striped
/>Relation Cells
Relation cells use an async onSearch callback to link records.
| Project | Status | Owner | Contributors |
|---|---|---|---|
phoundry-ui | |||
phials-core | |||
pheeder |
Show code
const columns = [
{ id: 'name', header: 'Project', accessorKey: 'name',
cellType: 'text' },
{ id: 'owner', header: 'Owner', accessorKey: 'owner',
cellType: 'relation',
cellConfig: { onSearch: searchPeople } },
{ id: 'contributors', header: 'Contributors',
accessorKey: 'contributors', cellType: 'relation',
cellConfig: { onSearch: searchPeople } },
];
<DataTable
data={projects}
columns={columns}
onCellChange={handleChange}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| data required | T[] | — | Array of row objects. |
| columns required | ColumnDef<T>[] | — | Column definitions controlling headers, accessors, and behavior. |
| getRowId | (row: T, index: number) => string | — | Custom row identity function. Defaults to index. |
| onSortChange | (sort) => void | — | Called when a sortable column header is clicked. |
| onSelectionChange | (ids: Set<string>) => void | — | Called when row selection changes. |
| onRowClick | (row: T, index: number) => void | — | Single-click handler for a row. |
| onRowDblClick | (row: T, index: number) => void | — | Double-click handler for a row. |
| onCellChange | (row: T, column, value) => void | — | Called when a built-in cell editor changes a value. |
| selectable | boolean | false | Show selection checkboxes. |
| rowHeight | number | 36 | Row height in pixels. |
| maxHeight | number | — | Max table height before scrolling. |
| virtualScroll | boolean | false | Enable virtual scrolling for large datasets. |
| striped | boolean | false | Alternate row background colors. |
| bordered | boolean | false | Add cell borders. |
| compact | boolean | false | Reduce cell padding. |
| class | string | — | Additional CSS classes. |
| groupBy | string | — | Column id to group rows by. Inserts a header row per group using the same cell visuals in read-only mode. Unknown ids are ignored. |
| groupHeaderHeight | number | — | Height in pixels for group header rows when using groupBy (defaults to rowHeight). |
| groupCell | Snippet | — | Optional custom renderer for group headers when both groupBy and the cell snippet are set; otherwise a text fallback is used. |
| cell | Snippet<[value, row, column, rowIndex]> | — | Overrides built-in cell rendering for every column. |
| headerCell | Snippet<[ResolvedColumn<T>]> | — | Custom header cell content per column definition. |
| empty | Snippet | — | Shown when `data` is empty. |
ColumnDef
| Prop | Type | Default | Description |
|---|---|---|---|
| id required | string | — | Unique column identifier. |
| header required | string | — | Column header text. |
| accessorKey | keyof T | — | Property key to read from each row. |
| accessorFn | (row: T) => any | — | Custom accessor function. |
| sortFn | (a, b) => number | — | Custom sort comparator. |
| width | number | — | Fixed column width in pixels. |
| minWidth | number | — | Minimum resize width. |
| maxWidth | number | — | Maximum resize width. |
| sortable | boolean | — | Enable sorting for this column. |
| resizable | boolean | — | Enable column resizing. |
| align | 'left' | 'center' | 'right' | — | Cell text alignment. |
| cellType | CellType | — | Built-in cell editor type. Auto-renders matching cell component when no cell snippet is provided. |
| cellConfig | CellConfig | — | Configuration object passed to the auto-rendered cell editor (options, placeholder, callbacks, etc.). |
Cell Types
Available cellType values:
text— Click-to-edit text inputnumber— Click-to-edit number (supports decimals)boolean— Always-interactive checkboxrating— Always-interactive star ratingselect/status— Single-select dropdown with colored option pillsmulti-select— Multi-select dropdown with colored tagsdate— Date/datetime picker with optional end date and timeurl— URL display with click-to-edit and external linkrelation— Generic link picker with async search
Usage tips
- For headless usage without the rendered table, use
createTablefromimport { createTable } from 'phoundry-ui'. - Enable
virtualScrollwith amaxHeightfor datasets over a few hundred rows. - Provide
getRowIdwhen rows have a natural unique key — it improves selection and reconciliation. - Combine
stripedandcompactfor dense data displays like logs or analytics. - Cell editors can also be used standalone via the
cellsnippet: importCellText,CellSelect, etc. from'phoundry-ui'.