Phoundry UI

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 Email Role Status
Alice Chen[email protected]EngineerActive
Bob Rivera[email protected]DesignerAway
Carol Kim[email protected]ManagerActive
Dan Okafor[email protected]EngineerOffline
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 Email Role Status
Alice Chen[email protected]EngineerActive
Bob Rivera[email protected]DesignerAway
Carol Kim[email protected]ManagerActive
Dan Okafor[email protected]EngineerOffline
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 Email Role Status
Alice Chen[email protected]EngineerActive
Bob Rivera[email protected]DesignerAway
Carol Kim[email protected]ManagerActive
Dan Okafor[email protected]EngineerOffline

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

PropTypeDefaultDescription
data requiredT[]Array of row objects.
columns requiredColumnDef<T>[]Column definitions controlling headers, accessors, and behavior.
getRowId (row: T, index: number) => stringCustom row identity function. Defaults to index.
onSortChange (sort) => voidCalled when a sortable column header is clicked.
onSelectionChange (ids: Set<string>) => voidCalled when row selection changes.
onRowClick (row: T, index: number) => voidSingle-click handler for a row.
onRowDblClick (row: T, index: number) => voidDouble-click handler for a row.
onCellChange (row: T, column, value) => voidCalled when a built-in cell editor changes a value.
selectable booleanfalseShow selection checkboxes.
rowHeight number36Row height in pixels.
maxHeight numberMax table height before scrolling.
virtualScroll booleanfalseEnable virtual scrolling for large datasets.
striped booleanfalseAlternate row background colors.
bordered booleanfalseAdd cell borders.
compact booleanfalseReduce cell padding.
class stringAdditional CSS classes.
groupBy stringColumn 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 numberHeight in pixels for group header rows when using groupBy (defaults to rowHeight).
groupCell SnippetOptional 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 SnippetShown when `data` is empty.

ColumnDef

PropTypeDefaultDescription
id requiredstringUnique column identifier.
header requiredstringColumn header text.
accessorKey keyof TProperty key to read from each row.
accessorFn (row: T) => anyCustom accessor function.
sortFn (a, b) => numberCustom sort comparator.
width numberFixed column width in pixels.
minWidth numberMinimum resize width.
maxWidth numberMaximum resize width.
sortable booleanEnable sorting for this column.
resizable booleanEnable column resizing.
align 'left' | 'center' | 'right'Cell text alignment.
cellType CellTypeBuilt-in cell editor type. Auto-renders matching cell component when no cell snippet is provided.
cellConfig CellConfigConfiguration object passed to the auto-rendered cell editor (options, placeholder, callbacks, etc.).

Cell Types

Available cellType values:

  • text — Click-to-edit text input
  • number — Click-to-edit number (supports decimals)
  • boolean — Always-interactive checkbox
  • rating — Always-interactive star rating
  • select / status — Single-select dropdown with colored option pills
  • multi-select — Multi-select dropdown with colored tags
  • date — Date/datetime picker with optional end date and time
  • url — URL display with click-to-edit and external link
  • relation — Generic link picker with async search

Usage tips

  • For headless usage without the rendered table, use createTable from import { createTable } from 'phoundry-ui'.
  • Enable virtualScroll with a maxHeight for datasets over a few hundred rows.
  • Provide getRowId when rows have a natural unique key — it improves selection and reconciliation.
  • Combine striped and compact for dense data displays like logs or analytics.
  • Cell editors can also be used standalone via the cell snippet: import CellText, CellSelect, etc. from 'phoundry-ui'.