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/07/31
Write documentation
4
Set date...
Deploy staging environment
2
2026/07/27
Design system review
0
2026/08/04
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/07/27
In Progress
Build DataTable cell editors
8
2026/07/31
Review
Design system review
0
2026/08/04
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) => 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 booleanfalseShow selection checkboxes.
rowHeight number36Row height in pixels.
maxHeight number-Max 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 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

PropTypeDefaultDescription
id requiredstring-Unique column identifier.
header requiredstring-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 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'.