GitHubGitHub
← All components
Data / component

Data table

A sortable, filterable table: per-column funnel filters (text, set, number, date), snippet cell renderers, sticky header, pagination, responsive card mode, and keyboard-activatable rows.

SortableFilteredPaginatedCards on phonesEmpty
Services
Unit
Requests428.6/s
Response time82.1ms
const filters = createColumnFilters([  { key: 'kind', type: 'set' },  { key: 'value', type: 'number' }]);<DataTable {columns} {rows} {filters} defaultSort="value" onRowClick={open} />

Installation

pnpm add @chanrg/4ui

Import the stylesheet once, at the root of your app:

import '@chanrg/4ui/styles.css';

Usage

import { DataTable } from '@chanrg/4ui';const filters = createColumnFilters([  { key: 'kind', type: 'set' },  { key: 'value', type: 'number' }]);<DataTable {columns} {rows} {filters} defaultSort="value" onRowClick={open} />

Composition

Use the following composition to build a data table:

DataTable
├── columns[].cell (snippet)
├── ColumnFilter (via filters)
└── Pager (via pageSize)

Paginated

Set pageSize to page the sorted rows; page is bindable and clamps when a filter shrinks the set.

Metrics
Unit
Cache hit rate%
Requests428.6/s

Page 1 of 3

<DataTable {columns} {rows} pageSize={2} bind:page />

Compact density

Tightens cell padding for dense management screens.

Metrics
Unit
Requests428.6/s
Response time82.1ms
CPU load78.4%
Queue depth3.1k
<DataTable {columns} {rows} density="compact" />

Cards on phones

With mobileCards, each row becomes a labelled card below 480px; columns place themselves via mobileRole.

Metrics
Unit
Requests428.6/s
Response time82.1ms
CPU load78.4%
Queue depth3.1k

Below 480px each row becomes a labelled card.

const columns = [  { key: 'name', label: 'Service', mobileRole: 'primary' },  { key: 'value', label: 'Value', numeric: true },  { key: 'unit', label: 'Unit', hideBelow: 550 }];<DataTable {columns} {rows} mobileCards />

API reference

Props

PropTypeDefaultDescription
columnsDataTableColumn[][]key, label, sortable, numeric, width, value(), format(), or a cell snippet.
rowsRow[][]Caller-owned data.
filtersColumnFiltersController from createColumnFilters(); adds funnel popovers to columns.
defaultSort / defaultDirstring / asc | desc— / descInitial sort.
onRowClick(row) => voidMakes rows interactive (click + Enter/Space).
maxHeightnumber280Scroll height in px; 0 grows freely.
localestringruntimeCollation locale for text sorting.
captionstringScreen-reader description.
pageSize / pagenumber | null / numbernull / 1Paginates the sorted rows; `page` is bindable and clamps when filters shrink the set.
densityregular | compactregularTightens cell padding for dense management screens.
mobileCardsbooleanfalseBelow 480px each row becomes a card; columns place themselves via column.mobileRole.
column.mobileRoleprimary | control | actions | full | hiddenCard placement. Unset renders a labelled meta cell.
column.hideBelow550 | 700Drops the column under that viewport width.

CSS variables

VariableDescription
--4ui-surfacePrimary component surface.
--4ui-textPrimary foreground color.
--4ui-borderDefault control and panel border.

Accessibility

  • Sort headers are real buttons and the active column exposes aria-sort.
  • Filter panels are dialogs that close on Escape and outside pointer.
  • Custom cells use snippets — no HTML string injection.
  • Card mode labels every value with its column name via a data-label pseudo-element.

Dependencies

Icon, Pager, dismiss/portal/floating primitives