Pathfinderby Commenda
Components

Table

Build record tables with sorting, search, filters, pagination, selection and row actions.

Invoice
Customer
Amount (USD)
INV-001Meridian GmbH2400
INV-002Meridian US Inc.1800
INV-003Meridian UK Ltd.950
1–3 of 3
Rows per page

Use Table for records people find, open and act on. Start with rows, typed columns, and stable row identities, then add the features your screen needs. Use DataGrid for financial matrices with grouped headers and keyboard cell navigation.

Headers and alignment

Select current page
Select INV-002INV-002OpenUSD2,150
Select INV-003INV-003OpenUSD3,200
Select INV-001INV-001PaidUSD4,800
1–3 of 3
Rows per page

Headers are 36px high with 12px medium labels and 12px icons. Hover or focus an unsorted header to reveal the neutral sort indicator; the active column keeps its direction visible. Click to cycle sorting. End-aligned numeric columns place sorting before the label. Supply description for a separate, keyboard-accessible information tooltip. Selection headers use the same horizontal inset as row checkboxes.

Usage

Example.tsx
'use client';

import { Table } from '@commenda/pathfinder/table';

const invoices = [
  { id: 'inv-001', number: 'INV-001', customer: 'Meridian UK Ltd' },
  { id: 'inv-002', number: 'INV-002', customer: 'Meridian US Inc.' },
];

export default function InvoicesTable() {
  return (
    <Table
      label="Invoices"
      rows={invoices}
      rowConfig={{ getId: (invoice) => invoice.id }}
      columns={[
        { id: 'number', header: 'Invoice', value: (invoice) => invoice.number },
        {
          id: 'customer',
          header: 'Customer',
          value: (invoice) => invoice.customer,
        },
      ]}
    />
  );
}

Import @commenda/pathfinder/styles.css once in your app layout. Pathfinder ships its own compiled styles. Open Code on any example for the complete, formatted implementation; the snippet comes from the same source as its preview.

Columns define a stable id, a visible header, and a value(row) accessor. Add cell(row) when rendering richer content. Keep the accessor when sorting or searching should use the underlying value rather than its formatted label. rowConfig.getId must return a stable record ID, not an array index.

Sorting

INV-004Meridian SingaporeUSD3,200.00
INV-001Meridian GmbHUSD2,400.00
INV-002Meridian US Inc.USD1,800.00
INV-003Meridian UK Ltd.USD950.00
1–4 of 4
Rows per page

Sorted by amount, descending

Click Amount to cycle descending, unsorted and ascending order. The initial sort is controlled by sortingConfig; the status below the example reflects the current value. Numeric accessors sort numerically even when the cell formats money. Local sorting runs before pagination.

Search and filters

Invoice
Customer
Status
INV-001Meridian GmbHPaid
INV-002Meridian US Inc.Pending
INV-003Meridian UK Ltd.Overdue
INV-004Meridian SingaporePending
1–4 of 4
Rows per page

Open search and enter Meridian UK, or choose Pending from Status. Search and filters combine. Search opens in place; tools stay together and excess filter buttons move into +N. A query with no matches offers Clear filters. This example supplies getText explicitly so invoice numbers and customer names are searchable.

For a local collection, matches(row, values) defines filter membership. In remote mode the server applies it; use controlled search/filter state as request inputs. Reset the page when an externally controlled filter changes.

Single-choice filters

Invoice
Status
INV-001Paid
INV-002Open
INV-003Overdue
1–3 of 3
Rows per page

Choose a status. Single choice replaces the previous selection, shows a checkmark and closes its menu. Clear resets that field. The selected value becomes an editable chip below the toolbar.

Complex filters

Filing
Entity
Status
Due
Amount
Annual accountsAcme GmbHDue soon2026-11-14USD95
Delaware annual reportAcme Inc.Overdue2026-10-01USD450
BOI report updateAcme Inc.Due soon2026-11-14USD0
1–3 of 3
Rows per page

Fields combine with AND. Selected values within a field combine with OR. Date presets use a fixed demonstration period.

This implements Option A from Figma: one compound chip per active filter, with field · operator · value · remove. Change Status from “is either of” to “is not”, search Entity options, or open Due date to choose a preset or calendar range. Use More filters to add Assignee or Amount; the field editor opens immediately. Resize the container to reveal the +N filter overflow menu.

The toolbar keeps quiet field buttons. Applied chips wrap below it. Multiple choices stay checked while searching, selected options appear first, and country flags lead entity names. Each menu clears only its own field; Clear filters resets the whole filter map. Date changes apply once a complete range exists. Numeric conditions apply after validation.

Fields combine with AND; multiple selected choices within one field combine with OR. “is not” negates membership. The app controls the value map and can control operators independently. For remote tables, serialize both into your API request and query-selection key; the table never re-filters a server page. Missing dates/numbers do not match a numeric/date condition. Arbitrarily nested AND/OR expressions are not part of this field-filter API.

Pagination

Invoice
Customer
Amount (USD)
INV-001Meridian GmbH250
INV-002Meridian US Inc.500
INV-003Meridian UK Ltd.750
INV-004Meridian GmbH1000
INV-005Meridian US Inc.1250
1–5 of 12
Rows per page

Move through twelve records in pages of five, then change Rows per page. The example controls { page, pageSize } and offers 5/10 for demonstration. Defaults are page 1, 25 rows, and 25/50/100 choices. Filtering and sorting apply to the full local collection before it is sliced.

Column visibility

Invoice
Customer
INV-001Meridian GmbH
INV-002Meridian US Inc.
INV-003Meridian UK Ltd.
1–3 of 3
Rows per page

Hidden columns: amount

Open Columns and enable Amount. Invoice cannot be hidden because it identifies the record. The controlled viewConfig.hiddenColumns array can be stored in application preferences; column IDs should survive label changes and translations. Density and visibility can be controlled independently.

Column pinning

Select current page
Invoice
Customer
Entity
Owner
Status
Amount
Select INV-001INV-001Meridian GmbHGermanyLucas MoreauPaidUSD4,800
Select INV-002INV-002Meridian US Inc.United StatesPriya ShahOpenUSD2,150
Select INV-003INV-003Meridian UK Ltd.United KingdomAisha KhanOpenUSD3,200
1–3 of 3
Rows per page

Scroll horizontally. Invoice starts pinned; Amount ends pinned. Change pins from Columns.

Scroll horizontally: Invoice stays at the logical start and Amount at the end. Open Columns, then a column's pin control to move it or unpin it. Selection and action cells remain at their edges alongside pinned data. Hidden columns do not leave gaps. Pinning uses logical edges in RTL and releases in a narrow container when the pinned regions would obscure readable data.

Use column.pin for a fixed default or viewConfig.columnPinning for interactive preferences. Its value is a map of column IDs to 'start', 'end', or null (explicitly unpinned). pinnable: false locks a column's configured position.

Drag column widths

Invoice
Customer
Owner
Amount
INV-001Meridian GmbHLucas MoreauUSD4,800
INV-002Meridian US Inc.Priya ShahUSD2,150
INV-003Meridian UK Ltd.Aisha KhanUSD3,200
1–3 of 3
Rows per page

Customer width: 280px. Drag a header edge, or focus it and use Left/Right (10px), Shift (50px), Home/End.

Drag a header's trailing edge to increase or decrease its width. Focus the handle to use Left/Right in 10px steps, Shift for 50px, or Home/End for the bounds. Escape during a drag restores its starting width. Resize does not trigger sorting; pinned offsets follow the updated widths.

Enable handles with viewConfig.columnWidths. Set each column's width, minWidth, maxWidth, and optionally resizable: false. The default bounds are 80–800px; choose bounds appropriate to the content, especially money and dates. Controlled width and pin maps can be saved in application preferences. Explicit layout widths preserve accurate drag distances and pin offsets; a table can leave unused space when its configured total is narrower than its container.

Row selection

Select current page
Invoice
Customer
Status
Select INV-001INV-001Meridian GmbHPaid
Select INV-002INV-002Meridian US Inc.Pending
1–2 of 4
Rows per page

Select unpaid invoices to request a reminder.

Select an unpaid invoice, move to the next page, or choose Select all 3 in the bulk bar. Paid invoices are ineligible. Selection persists by stable ID. Request reminder updates the status message only; this demo does not send messages.

TableSelection is { kind: 'ids', ids } or { kind: 'all', queryKey, excludedIds }. The header checkbox selects the current eligible page. allMatching explicitly enables the entire eligible query, including unloaded pages. Pass a new query key when filter membership or authorization scope changes. The server must interpret that query/exclusion model and recheck permissions.

Row actions and copying

Invoice
Customer
Status
Actions
INV-001Meridian GmbHPaid
INV-002Meridian US Inc.Pending
INV-003Meridian UK Ltd.Overdue
INV-004Meridian SingaporePending
1–4 of 4
Rows per page

Open a row or use its menu.

Click a record to open it, use its overflow menu, or hover/focus the invoice number to copy it. Those controls do not also activate the row. Download receipt is unavailable before payment. The example reports requested actions locally; real navigation/downloads belong to the application.

Use specific action names and at most a primary icon, secondary icon and overflow per row. Keep whole-record editing in a Sheet; reserve inline editing for frequently changed fields.

Custom cells

Select current page
Entity
Assignee
Status
Actions
Select Delaware annual reportDelaware annual reportAnnual compliance · 2026Meridian US Inc.Priya ShahIn progress2026-10-15
Select VAT return Q3VAT return Q3Quarterly tax returnMeridian GmbHLucas MoreauTo do2026-10-20
Select Corporate income taxCorporate income taxFinancial year 2025Meridian US Inc.Aisha KhanDone2026-10-25
Select Financial statementsFinancial statementsAnnual financial reportingMeridian GmbHJun TanTo do2026-10-30
1–4 of 4
Rows per page

This filing table combines descriptions, country flags, assignees, status badges and row actions in a real record layout. cell(row) composes existing Pathfinder components without changing the underlying record shape. Entity names use flags; people use Avatar. System status uses Badge, while selected removable values use Tag.

Text aligns at the leading edge, numbers at the trailing edge, and badges align with their header. Missing values use an en dash, not zero. Use TableNumberCell for consistent decimals and ISO currency labels. Long names can grow a row rather than lose essential content.

Grouped records

Select current page
Filing
Assignee
Status
Fee
Actions
Select VAT return Q3VAT return Q3Monthly · Federal Central Tax OfficeLucas MoreauFiledEUR0
Select Trade tax returnTrade tax returnAnnual · MunichPriya ShahIn reviewEUR180
Select Annual accountsAnnual accountsAnnual · BundesanzeigerAisha KhanNot startedEUR95
Select Delaware annual reportDelaware annual reportAnnual · DelawareJun TanOverdueUSD450
Select BOI report updateBOI report updateOne-time · FinCENMira de VriesDue soonUSD0
1–5 of 5
Rows per page

Collapse a group, select records, then reopen it. Selection persists; counts describe the current page.

Group filings by entity with a flag, count and read-only summary. Activate a group header to collapse it. Selection survives collapse, and the header checkbox still selects eligible records on the current page. Search, sort and pagination operate before grouping; page counts describe records, never group headers. A large entity may span pages. The summary callback receives the current page's group records; pass app-owned aggregates if the summary must describe the entire query.

Use groupingConfig.getId, getLabel, optional leading, summary, and controlled/uncontrolled collapsedGroups. For recursive financial rows, use DataGrid hierarchy.

Toolbar actions

Document
Articles of association.pdf
Register of directors.pdf
1–2 of 2
Rows per page

Demonstration actions update this message. Your application supplies the upload, export and folder workflows.

The right side contains tools, overflow, a divider, then at most one secondary and one primary action. Export becomes an icon when actions are present. A primary action with menu renders a SplitButton: the main button performs the default action, while the arrow exposes alternatives. Selection actions belong in the bulk bar.

Use named primaryAction and secondaryAction; put extra actions in moreMenu. These examples report callbacks locally. Your application supplies upload, export and navigation workflows.

Documents

Document
Entity
Reference
Uploaded by
Delaware annual report.pdfAcme GmbHHRB 284731Priya Shah2026-10-02
Articles of association.pdfAcme Inc.DE-7745120Lucas Moreau2026-09-28
1–2 of 2
Rows per page

Copy a reference or change the upload-date sort.

File icons identify documents, country flags identify entities, and avatars identify uploaders. References expose a copy action on hover, focus or touch. The demo writes the selected reference to the clipboard and reports failures.

Team members

Member
Role
Status
2FA
Last active
Actions
Priya Shahpriya@acme.comAdminActiveToday
Lucas Moreaulucas@acme.comMemberActiveYesterday
Aisha Khanaisha@acme.comRead onlyInvited–
Jun Tanjun@acme.comMemberDeactivated12 Aug 2026
1–4 of 4
Rows per page

Roles are values (Tags); account statuses are read-only Badges.

People include their email, role as a Tag, account status as a Badge and an accessible yes/no symbol for two-factor authentication. Missing activity is an en dash. Row-specific actions live in the overflow menu.

Density and containers

Filing
Status
VAT return Q3In review
Annual accountsFinancial year ending 31 Dec 2026In review
1–2 of 2
Rows per page

Use the view menu to compare compact and relaxed density, and the button to switch card/flat containers. The second record includes a description to show the two-line behavior.

MeasurementCompactRelaxed
Header36px36px
Single-line row minimum40px52px
Two-line text row52px64px
Horizontal cell padding12px12px

A card adds a 12px radius and hairline border. Use flat inside an existing surface. Wrapped content, text enlargement and taller custom controls can increase these heights.

Presets and summary

Matching records 4Total due $6,400.00
Filing
Amount
Status
Annual accountsUSD2,400Overdue
VAT return Q3USD1,800Due soon
Trade tax returnUSD950Filed
Corporate income taxUSD1,250Filed
1–4 of 4
Rows per page

Choose Overdue, then clear its toolbar filter. Preset cards and filters share state, so the selected preset updates with the query. The summary is read-only. Cards sit above the table; summary content lives inside it.

This example's summary follows the status filter. Production summaries should use aggregates for the same reporting query, including search when applicable, rather than summing one loaded API page. Preset definitions, counts and persistence are app-owned.

Editable cells and totals

Account
Amount
TotalUSD4,200.00
1–2 of 2
Rows per page

All changes saved

Hover a row, click an account or amount, and edit it. Enter saves, Escape cancels, and Add line appends a record. Totals recompute from application data and align by column ID through footerConfig.total.cells. The first visible column contains the total label.

The example saves in memory. An async onCommit must resolve only after persistence succeeds and reject on failure; the editor retains rejected drafts and offers Retry save. Text/number fields use 28px compact geometry. Select and DatePicker preserve their existing accessible dimensions and can increase row height.

Account mapping and validation

Keep fields outlinedSimulate save failure
Account
Name
Mapping
Allocation
Amount
6100Office rent
EUR48,000.00
6200Software subscriptions
EUR21,340.50
5000Cost of goods sold
EUR412,880.00
6400Shared services fee
EUR96,000.00
TotalEUR578,220.5
1–4 of 4
Rows per page

The last row has an invalid allocation. Edit it to correct the value.

This account-mapping example combines Select and numeric editors, an existing invalid allocation, aligned money totals and Add line. Try Keep fields outlined for form-style tables. Simulate save failure retains the draft and exposes retry; disable the simulation to complete the save. The app owns validation and financial calculations.

Dates, switches and attachments

Filing
Due date
Reminders
Attachment
VAT return Q3
Send VAT reminders
1–1 of 1
Rows per page

Date, reminder and attachment changes are local to this example.

Compose DatePicker editing, an immediate Switch and an upload action directly in cells. The picker keeps Pathfinder's approved DateField geometry. Once a PDF is chosen, its filename replaces the upload action. File selection is local in this example; no bytes leave the browser. Real uploads need application-side file validation, progress, persistence and failure handling.

API-controlled data

Filing
Delaware annual report
VAT return Q3
Corporate income tax
Financial statements
1–4 of 4
Rows per page

dataMode="remote" displays exactly the rows returned by the app. Table never sorts, filters or slices that received page again. The example uses synchronous fixture filtering to demonstrate state ownership, not a production API adapter.

Use search, filters, sort and pagination as a request key. Keep typing responsive, debounce requests rather than the displayed value, cancel obsolete requests, and prevent stale responses from replacing newer results. Remote numbered pages require paginationConfig.totalRows. URL persistence, exports and server authorization remain application responsibilities.

Cursor pagination

Invoice
Customer
INV-001Meridian GmbH
INV-002Meridian US Inc.
INV-003Meridian UK Ltd.
INV-004Meridian GmbH
Showing 4 records

Use Previous and Next without an invented total. The demo simulates server pages; real applications hold opaque API cursors. Supply hasNext, hasPrevious, callbacks and an honest summary. Pair cursor configuration with dataMode="remote".

Loading, empty and error

Filing
0–0 of 0
Rows per page

Switch between loading skeletons, first-use emptiness and a retryable error. Search/filter emptiness instead offers Clear filters. Keep toolbar and column geometry stable while requests run. Retry should repeat the current query, not discard the user's scope.

Accessibility and responsive behavior

Table retains native table semantics. Checkbox selection is separate from row activation; a focused activatable row opens with Enter. Search shortcuts apply while focus is in table tools. The scroll region supports keyboard access. Narrow layouts scroll within the component and release pinned columns if they would hide readable values.

Read the Figma rules and explicit differences before changing dimensions or behavior.

API reference

groupingConfig groups the current page: { getId(row), getLabel(id), leading?(id), summary?(id, pageRows), collapsedGroups? }. Collapsed state is a list of group IDs using the same value / onValueChange or defaultValue convention as other preferences.

TableFilter is discriminated by type:

PropTypeBehavior
labelRequiredstringVisible field label. Supply an accessible name when it is omitted.
rowsRequiredreadonly TRow[]—
columnsRequiredreadonly TableColumn<TRow>[]—
rowConfigRequiredObjectView fields—
viewConfigObjectView fields—
groupingConfigObjectView fields—
container"card"|"flat"—
sortingConfigObjectView fields · 2 shapes—
searchConfigSelectionProps<string> & { getText?: | ((row: TRow) => string) | undefined; placeholder?: string; }—
filterConfigObjectView fields—
selectionConfig
View full typeSelectionProps<TableSelection> & { canSelect?: | ((row: TRow) => boolean) | undefined; allMatching?: { queryKey: string; total: number; }; actions?: readonly TableAction[]; moreMenu?: DropdownMenu; }
—
toolbarConfigTableToolbarConfigView fields—
stateConfigTabularStateConfigView fields—
summaryReactNode—
footerConfigObjectView fields—
classNamestringAdditional classes for the component root.
dataModeConditional"local"|"remote"—
paginationConfigConditionalObjectView fields—
rowConfigObject fields
FieldTypeRequirement
getId(row: TRow) => stringRequired
getLabel(row: TRow) => stringRequired
onActivate((row: TRow) => void)Optional
menu((row: TRow) => DropdownMenu)Optional
viewConfigObject fields
FieldTypeRequirement
densityObjectOptional
hiddenColumnsObjectOptional
columnWidthsObjectOptional
columnPinningObjectOptional
viewConfig.densityObject fields

Choose one of these 2 shapes. Do not combine fields excluded by the selected shape.

Option 1

FieldTypeRequirement
value"compact" | "relaxed"Required
onValueChange(value: "compact" | "relaxed") => voidRequired

Do not supply: defaultValue.

Option 2

FieldTypeRequirement
defaultValue"compact" | "relaxed"Optional
onValueChange((value: "compact" | "relaxed") => void)Optional

Do not supply: value.

viewConfig.hiddenColumnsObject fields

Choose one of these 2 shapes. Do not combine fields excluded by the selected shape.

Option 1

FieldTypeRequirement
valuereadonly string[]Required
onValueChange(value: readonly string[]) => voidRequired

Do not supply: defaultValue.

Option 2

FieldTypeRequirement
defaultValuereadonly string[]Optional
onValueChange((value: readonly string[]) => void)Optional

Do not supply: value.

viewConfig.columnWidthsObject fields

Choose one of these 2 shapes. Do not combine fields excluded by the selected shape.

Option 1

FieldTypeRequirement
valueReadonly<Partial<Record<string, number>>>Required
onValueChange(value: Readonly<Partial<Record<string, number>>>) => voidRequired

Do not supply: defaultValue.

Option 2

FieldTypeRequirement
defaultValueReadonly<Partial<Record<string, number>>>Optional
onValueChange((value: Readonly<Partial<Record<string, number>>>) => void)Optional

Do not supply: value.

viewConfig.columnPinningObject fields

Choose one of these 2 shapes. Do not combine fields excluded by the selected shape.

Option 1

FieldTypeRequirement
valueReadonly<Partial<Record<string, "start" | "end" | null>>>Required
onValueChange(value: Readonly<Partial<Record<string, "start" | "end" | null>>>) => voidRequired

Do not supply: defaultValue.

Option 2

FieldTypeRequirement
defaultValueReadonly<Partial<Record<string, "start" | "end" | null>>>Optional
onValueChange((value: Readonly<Partial<Record<string, "start" | "end" | null>>>) => void)Optional

Do not supply: value.

groupingConfigObject fields
FieldTypeRequirement
getId(row: TRow) => stringRequired
getLabel(id: string) => stringRequired
leading((id: string) => ReactNode)Optional
summary((id: string, pageRows: readonly TRow[]) => ReactNode)Optional
collapsedGroupsObjectOptional
groupingConfig.collapsedGroupsObject fields

Choose one of these 2 shapes. Do not combine fields excluded by the selected shape.

Option 1

FieldTypeRequirement
valuereadonly string[]Required
onValueChange(value: readonly string[]) => voidRequired

Do not supply: defaultValue.

Option 2

FieldTypeRequirement
defaultValuereadonly string[]Optional
onValueChange((value: readonly string[]) => void)Optional

Do not supply: value.

sortingConfigObject fields

Choose one of these 2 shapes. Do not combine fields excluded by the selected shape.

Option 1

FieldTypeRequirement
valueObjectRequired
onValueChange(value: TableSort | null) => voidRequired

Do not supply: defaultValue.

sortingConfig.valueObject fields
FieldTypeRequirement
columnIdstringRequired
direction"desc" | "asc"Required

Option 2

FieldTypeRequirement
defaultValueObjectOptional
onValueChange((value: TableSort | null) => void)Optional

Do not supply: value.

sortingConfig.defaultValueObject fields
FieldTypeRequirement
columnIdstringRequired
direction"desc" | "asc"Required
filterConfigObject fields
FieldTypeRequirement
filtersreadonly TableFilter<TRow>[]Required
operatorsObjectOptional
valueReadonly<Record<string, readonly string[]>>Required
onValueChange(value: Record<string, readonly string[]>) => voidRequired
filterConfig.operatorsObject fields

Choose one of these 2 shapes. Do not combine fields excluded by the selected shape.

Option 1

FieldTypeRequirement
valueReadonly<Record<string, TableFilterOperator>>Required
onValueChange(value: Readonly<Record<string, TableFilterOperator>>) => voidRequired

Do not supply: defaultValue.

Option 2

FieldTypeRequirement
defaultValueReadonly<Record<string, TableFilterOperator>>Optional
onValueChange((value: Readonly<Record<string, TableFilterOperator>>) => void)Optional

Do not supply: value.

toolbarConfigObject fields
FieldTypeRequirement
actionsreadonly TableAction[]Optional

Prefer named actions. Legacy actions use first=primary, second=secondary, remainder=overflow.

primaryAction(TableAction & { menu?: DropdownMenu; icon?: ReactNode; })Optional
secondaryActionObjectOptional
onExport(() => void)Optional
moreMenuObjectOptional
toolbarConfig.secondaryActionObject fields
FieldTypeRequirement
idstringRequired
labelstringRequired
onAction() => voidRequired
disabledbooleanOptional
toolbarConfig.moreMenuObject fields
FieldTypeRequirement
groupsDropdownGroup[]Required
stateConfigObject fields
FieldTypeRequirement
isLoadingbooleanOptional
errorObjectOptional
emptyObjectOptional
stateConfig.errorObject fields
FieldTypeRequirement
messagestringRequired
onRetry() => voidRequired
stateConfig.emptyObject fields
FieldTypeRequirement
titlestringRequired
descriptionstringOptional
actionObjectOptional
stateConfig.empty.actionObject fields
FieldTypeRequirement
idstringRequired
labelstringRequired
onAction() => voidRequired
disabledbooleanOptional
footerConfigObject fields
FieldTypeRequirement
totalObjectOptional
addActionObjectOptional
footerConfig.totalObject fields
FieldTypeRequirement
labelstringRequired
cellsReadonly<Record<string, ReactNode>>Required
footerConfig.addActionObject fields
FieldTypeRequirement
idstringRequired
labelstringRequired
onAction() => voidRequired
disabledbooleanOptional
paginationConfigObject fields
FieldTypeRequirement
mode"cursor"Required
hasNextbooleanRequired
hasPreviousbooleanRequired
onNext() => voidRequired
onPrevious() => voidRequired
summarystringOptional
pageSizenumberRequired
onPageSizeChange((size: number) => void)Optional

All filters have id, label, optional icon, and initiallyVisible: false for fields added through More filters. filterConfig.operators uses a controlled/uncontrolled map of is, isNot, isAnyOf, within, greaterThan or lessThan; menus expose only operators meaningful for that field type. Range filters use inclusive endpoints. A custom choice matches predicate describes positive membership; isNot is applied by Table.

PropTypeBehavior
labelstringRequired accessible table name
rowsreadonly TRow[]Immutable local collection or one received API page
columnsreadonly TableColumn<TRow>[]IDs, headers, accessors and renderers
rowConfig{ getId, getLabel, onActivate?, menu? }Stable unique IDs; activation opens the record, checkbox selects it
dataMode'local' or 'remote'Local by default; remote never reprocesses the received page
viewConfig{ density?, hiddenColumns?, columnWidths?, columnPinning? }Each uses controlled value/onValueChange or defaultValue; compact and all columns visible by default
container'card' or 'flat'Card by default
sortingConfigSelectionProps<TableSort or null>Controlled value/onValueChange or defaultValue
searchConfig{ value?, defaultValue?, onValueChange?, getText?, placeholder? }Enables search; default local searchable text joins column values
filterConfig{ filters, value, onValueChange, operators? }Controlled filter map; typed choice/date/number definitions, optional controlled operators
paginationConfigPage or cursor configurationLocal page 1 and 25 rows by default
selectionConfigSelectionProps plus selection optionsStable IDs, optional eligibility, query-scoped select-all and bulk actions
toolbarConfig{ primaryAction?, secondaryAction?, onExport?, moreMenu?, actions? }App callbacks; exports never imply just the loaded rows
stateConfig{ isLoading?, error?, empty? }Skeleton rows, retryable failure or empty state
summaryReactNodeRead-only summary inside the container; put app preset controls above Table
footerConfig{ total?: { label, cells }, addAction? }App-owned totals keyed by column ID and an explicit add-row action; first visible column contains the total label
classNamestringRoot classes

TableColumn has id, string header, optional description tooltip, value(row), cell(row), width, minWidth, maxWidth in pixels, resizable, pinnable, align: 'start' | 'center' | 'end', sortable, compare(left, right), hideable, and pin: 'start' | 'end'. value supplies sortable/searchable data even when cell renders rich content. Pinned columns are grouped at their logical edge; horizontal pinning releases when it would leave insufficient space for a value column. Do not hide every column. Column IDs and row IDs must remain stable, nonempty and unique when updating data. Invalid IDs, empty column collections and invalid page numbers fail with configuration errors.

viewConfig.density uses compact or relaxed; viewConfig.hiddenColumns uses column IDs. Control either independently to persist user preferences. Non-hideable columns stay visible, and the first column remains as a safeguard if all hideable columns were requested hidden.

TableSort is { columnId, direction: 'asc' | 'desc' }; the sort control cycles ascending, descending, unsorted. Local search/filter/sort run before pagination. Search and sort changes return to the first page. App-controlled filters should reset the page when changed externally.

Cell renderers

TableCell is a discriminated content component; the owning table supplies padding and height:

typeContent props
text (default)value, description, leadingIcon, badge, tooltip, struck
entity / filelabel, icon, optional description
personAvatar identity, optional description
peoplelabel, AvatarGroup people
badge / tagitems using existing Badge/Tag props
booleanvalue, optional accessible label
progressexisting ProgressBar progress props
linklabel, linkConfig
copyvalue, onCopy, optional copyLabel
loadingSkeleton content
numberAll TableNumberCell props

TableNumberCell accepts value, optional locale, Intl.NumberFormat format, unit, suffix, tone, struck and trend: { direction: 'up' | 'down', intent: 'positive' | 'critical' | 'neutral' }. Numbers align right without truncation; unit labels stay separate. Empty and non-finite numeric values show an en dash. Numeric strings preserve their supplied formatting. Trend direction does not determine business meaning: rising costs may be critical.

TableEditCell accepts label, string value, editor, optional display, outlined, disabled, validationMessage, validate(value) and async onCommit(value). Editors are { type: 'text', placeholder? }, { type: 'number', min?, max?, step?, unit?, suffix? }, { type: 'select', options, placeholder? }, or { type: 'date', min?, max? }. Number drafts remain strings; validate and parse at the application boundary. Enter or leaving the editor saves; Escape cancels. Failed saves retain the draft and show Retry save. Select/calendar portals belong to the same editing session. DatePicker retains the approved Kakashi DateField padding. DataGrid manages editingConfig internally; ordinary Table renderers use the local editing state.

Design rules

Figma guidance and reviewed implementation decisions for this component. Rule IDs remain stable for source comments and AI references.

API references

The API review used shadcn's Data Table guide, TanStack controlled state and typed column definitions. Pathfinder adopts independently controlled state and reusable boundaries while retaining direct props, Figma styling and precompiled CSS. It keeps TanStack 8.21.3 implementation types private; the current shadcn guide's v9 code is not copied into this v8 integration. Matrix keyboard behavior follows the ARIA grid pattern.

Table

  • TABLE-001: White card, radius 12px, pearl-300 hairline 0.5px; optional flat container. Header 36px, compact row minimum 40px, relaxed 52px, horizontal padding 12px. Two-line text rows are 52px compact and 64px relaxed; longer wrapping and accessible controls can grow further. Toolbar and pagination minimum 48px.

  • TABLE-002: Hover and selected rows share pearl-200. Checkbox distinguishes selection; clicking the row opens the record. Links, editors, menus and checkboxes do not also activate the row. Enter opens a focused activatable row.

  • TABLE-003: Local processing is filter/search, sort, then page. Remote processing belongs entirely to the app; no double filtering, sorting or slicing. The app owns URL state, fetching, cancellation, permissions, exports and API cursors. Slash opens search while focus is within table tools. Escape clears then closes search. Search opens in place and its clear action clears and closes. Tools remain together; excess filter buttons use +N. Active compound filter chips wrap below the toolbar.

  • TABLE-004: Selection persists by stable IDs. All-matching selection requires a query key and eligible total, with explicit excluded IDs. Changing the query key invalidates stale all-selection. Bulk actions float 24px above the viewport bottom; three actions show directly and additional actions use overflow.

  • TABLE-005: Totals are supplied by column ID through footerConfig. The first visible column labels the total; Add line is a separate explicit application action.

  • TABLE-006: Column width handles support pointer capture, bounded drag, RTL, 10px arrow steps, Shift 50px, Home/End and Escape rollback. Widths and pinning can be controlled independently. Logical pin offsets include selection/action columns and follow resized or hidden columns.

  • TABLE-007: Group bars use a 2px inset, pearl-100 and radius 6px. Collapse preserves selection. Group only the current page; page counts and select-current-page refer to records, including collapsed descendants.

  • TABLE-008: Toolbar tools precede overflow and a divider; at most one secondary and one primary action follow. Export becomes an icon when actions are present. Primary plus alternatives uses SplitButton. Bulk actions never move into the ordinary toolbar.

  • FILTER-001: AND across fields, OR within multiple choices; isNot negates membership. Remote tables forward controlled values/operators to the application and never filter the received page.

  • FILTER-002: Menus align to their trigger at 4px offset. Single choice replaces; multiple choices retain selection while searching and show selected values first. Date presets/ranges reuse DateCalendar. Numeric ranges require valid ascending endpoints. Clear resets only the owning field.

  • FILTER-003: Option A chips are bordered, radius 6px and minimum 28px. Field/value reopen the editor; operator opens only compatible operators; remove clears that field. Separate chips wrap beneath the toolbar.

  • FILTER-004: More filters adds a secondary field and opens its editor immediately. +N exposes filters that do not fit. No active field becomes inaccessible when the container narrows.

  • TABLE-009: Header master 430:13536 / 428:76: 36px high, Label/12 Strong, 12px icons, 4px gaps, pearl-800 at rest and pearl-1000 for interactive hover/focus. The neutral selector appears on unsorted hover/focus; sorted direction stays visible. End-aligned headers place sorting before the label. Optional information has an independent keyboard-focusable tooltip. Table backgrounds are white; DataGrid headers retain pearl-100 and vertical rules.

Shared cells

  • CELL-001: Text starts at the logical leading edge; numbers align at the trailing edge and use tabular figures. Empty values show an en dash. Entity flags are 16px, person avatars 20px. Entity names wrap; do not truncate monetary amounts. Cells compose existing Badge, Tag, Avatar, ProgressBar and exact icon assets.
  • CELL-002: Currency units remain quiet and separate from values. Fixed decimals are supplied via Intl format options. Trend direction and semantic intent are separate. A cost increase can be critical even though its arrow points up.
  • CELL-003: At most primary bordered icon, secondary icon and overflow in a row action group. Do not repeat text CTAs in every row.
  • CELL-EDIT-001: Compact text/number editor minimum 28px and radius 6px; calm border at rest, outline on hover/focus, persistent invalid treatment. Existing Select and DatePicker keep their accessible field geometry; the DatePicker retains the user's approved Kakashi DateField padding rather than shrinking its segmented inputs.
  • CELL-EDIT-002: Enter or leaving the editor commits one cell; Escape cancels. Pending work cannot duplicate; errors retain drafts and offer retry. React portal focus stays within the same editing session. Application code validates and persists canonical values.

The Figma cell master depicts combinations, not 32 separate exported component classes. The shared content family composes those visual patterns. Toggle and upload actions use existing Switch/FileUpload application workflows in custom cells; they do not pretend to be string editors. TableCell content, TableNumberCell, TableRowActions and TableEditCell work in both owning components.

Explicit differences and boundaries

  • Selection: the Table prototype's row-click selection is superseded by its written best practice: row activation opens a record; checkbox selects. The implementation follows that distinction.

  • Accessibility: quiet text uses pearl-800 for contrast. Long content and text zoom can grow rows. Narrow viewports release horizontal pinning. Copy controls remain visible on touch, and long status labels expose their full text on focus/hover.

  • Application patterns: presets, summary aggregates, “Saved just now”, network requests, exports, permissions and financial calculations are owned by the app. Demos perform local updates or simulated requests and say so.

  • States and scale: matrix loading/error fallbacks reuse existing primitives. No virtualization, range operations or spreadsheet formulas are claimed. Stress fixtures do not establish an unlimited-data performance guarantee.

AI consumers should read this page together with the component API and integration pages. Do not infer a public prop from a Figma variant name, or treat a prototype's sample amounts as domain validation rules.

Header and cell master reconciliation

ReferenceCoverage
Header master 430:13536Start/center/end alignment, unsorted/ascending/descending, hover/focus, optional information, checkbox and empty action header; Headers, Selection and Pinning examples.
Groups and add row 445:13858Grouped filings show flags, counts, collapse and page-selection persistence; editable lines show totals and Add line. Application callbacks own actual persistence.

Long content and zoom can grow rows beyond Figma's nominal heights. Linear progress and ring progress use their existing Pathfinder primitives; cell recipes do not introduce a new progress implementation. A file-picker example selects a local file and displays its name; production upload, progress and retry belong to the application's upload workflow. These are deliberate compositional boundaries, not built-in backend features.

On this page