ComponentsData display

Data Table

The backbone of every list screen. Sorting (Shift for multi-sort), locale-aware search, column visibility, order, widths and pinning, row selection with a bulk bar that can select all matching rows across pages, pagination, server mode with URL state, inline editing with rollback, virtual rows, a container-width card layout and CSV export — on one serializable ListState shared with Filter bar and Saved views.

TanStack Table v9 + TanStack Virtual data-table/data-table.tsx, data-table/types.ts, data-table/filter-fns.ts, data-table/url-state.ts, data-table/csv.ts, styles/data.csstheme · material · accent · density · font
Posts
More actions
Writing release notes people read
Scheduled
Tom Brennan
Product
8,739
Hiring our first support engineer in Istanbul
Published
Tom Brennan
Company
9,207
A checklist for safer bulk actions
In review
Tom Brennan
Engineering
13,207
A field guide to customer interviews
Draft
Leila Haddad
Company
17,877
Localising for Turkish: İ, ı and friends
Scheduled
Selin Kaya
Customers
3,125
Designing empty states that convert
Published
Ada Park
Customers
13,718
What we learned shipping dark mode
In review
Selin Kaya
Customers
4,930
How we cut onboarding time in half
Published
Selin Kaya
Design
16,113
Five dashboards every ops team needs
Published
Marcus Webb
Company
15,076
Year in review: product
Published
Marcus Webb
Company
14,934

1–10 of 18

Installation

$ pnpm add @vamos-ui/react

The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";

Usage

import { DataTable, type DataTableColumn } from "@vamos-ui/react/data-table";

const columns: DataTableColumn<Contact>[] = [
  { id: "name", header: "Name", accessor: "name" },
  { id: "stage", header: "Stage", accessor: "stage", type: "enum", options: stageOptions },
  { id: "value", header: "Deal value", accessor: "value", type: "currency" },
];

<DataTable caption="Contacts" columns={columns} data={contacts} getRowId={(c) => c.id} selectable bulkActions={actions} />

TanStack Table v9 is isolated inside the component; you write DataTableColumn objects, not TanStack ColumnDefs.

Glass budget: rows, sticky headers and pinned cells are opaque mixes — the only blurred layer is the card (and the floating bulk bar on mobile).

Dates in data are YYYY-MM-DD strings; money is a decimal string. Both are formatted with the locale provider (try tr-TR: ; CSV delimiter, ₺, İ/ı-aware search).

Examples

Server mode + URL state (CRM)

12,480 contacts. onStateChange is the single entry point — apply only the latest requestId. Select the page, then “Select all 12,480”.

Contacts

No results

Filter bar + Saved views (HR)

Composed layout. Change a filter on a view to see Modified → Save / Save as new / Reset; the column menu’s “Filter by this column” opens the bar.

Employees
Jane Cooper
Support Lead
Support
Active
Ines Laurent
No
Kemal Rossi
Engineering Manager
Engineering
Active
Ines Laurent
No
Priya Mehta
Solutions Consultant
Sales
On leave
Kerem Yıldız
No
Lucas Iyer
Account Executive
Sales
Active
Ines Laurent
No
Amara Petrov
Solutions Consultant
Sales
Active
Kerem Yıldız
Yes
Elif Şahin
Brand Designer
Design
Active
Grace Whitfield
No
Noah Novak
Senior Engineer
Engineering
Active
Kerem Yıldız
No
Sofia Yıldız
Staff Engineer
Engineering
Active
Ines Laurent
No
Mateo Weber
Sales Development Rep
Sales
Active
Ines Laurent
No
Hana Moreau
Platform Engineer
Engineering
Active
Omar Haddad
No

1–10 of 48

Inline editing with rollback

Text, select, currency, date and switch editors. Optimistic save with a spinner; a rejected save restores the value and shows why.

Compensation
Jane Cooper
Kemal Rossi
Priya Mehta
Lucas Iyer
Amara Petrov
Elif Şahin
Noah Novak
Sofia Yıldız

Click a value to edit. Enter saves, Escape cancels. The fourth row is locked and rolls back.

Column management

Resize (drag, ←/→, double-click to fit), pin to start/end, hide, reorder and density from View. storageKey remembers it per browser.

Accounts
Tags
Jane Costa
Ostrava Labs
jane@ostravalabs.example
Lead
$1,006.00
Leila Haddad
Türkiye
—
Clara Novak
Fernhill Media
clara@fernhillmedi.example
Qualified
$8,798.00
Selin Kaya
Türkiye
ReferralTrial
Ravi Brooks
Ostrava Labs
ravi@ostravalabs.example
Qualified
$96,544.00
Selin Kaya
India
Referral
Clara Okafor
Ostrava Labs
clara@ostravalabs.example
Qualified
$18,966.00
Marcus Webb
Canada
RenewalEvent+2
Daniel Iyer
Pemberly Confectionery
daniel@pemberlyconf.example
Lead
$43,779.00
Leila Haddad
United States
—
Hugo Moreau
İstanbul Kahve Co.
hugo@istanbulkahv.example
Qualified
$91,394.00
Tom Brennan
Türkiye
Trial
Amara Whitfield
Ridgeway Logistics
amara@ridgewaylogi.example
Won
$64,896.00
Marcus Webb
Germany
Inbound
Sofia Silva
Pemberly Confectionery
sofia@pemberlyconf.example
Qualified
$43,100.00
Tom Brennan
Netherlands
PartnerEvent+1
Nadia Cooper
Ridgeway Logistics
nadia@ridgewaylogi.example
Lead
$43,979.00
Leila Haddad
Japan
Enterprise
Hana Şahin
Anadolu Lojistik
hana@anadolulojis.example
Lead
$34,911.00
Leila Haddad
Japan
InboundRenewal

1–10 of 40

10,000 rows, virtualised

virtual + pagination="none": only the visible rows are rendered, inside a native table with aria-rowcount / aria-rowindex.

Audit log
Selin Kaya
Updated
Invoice INV-2041
10.78.212.80
Ada Park
Signed in
Contact · Jane Cooper
10.26.21.194
Kerem Yıldız
Exported
Contact · Jane Cooper
10.139.231.104
Leila Haddad
Invited
Invoice INV-2041
10.86.200.211
Selin Kaya
Exported
API key ••••4f2a
10.139.104.24
Omar Haddad
Signed in
Role · Billing admin
10.186.222.40
Omar Haddad
Signed in
Report · Q3 pipeline
10.204.114.102
Marcus Webb
Deleted
Post · Release notes — November
10.191.197.213
Leila Haddad
Signed in
Report · Q3 pipeline
10.136.168.175
Selin Kaya
Signed in
Report · Q3 pipeline
10.241.236.4
Leila Haddad
Updated
Workspace settings
10.189.78.74
Selin Kaya
Invited
Role · Billing admin
10.96.245.111
Selin Kaya
Updated
Contact · Jane Cooper
10.98.210.137
Ada Park
Updated
Role · Billing admin
10.27.165.112
Marcus Webb
Exported
Invoice INV-2041
10.3.172.11
Grace Whitfield
Invited
Deal · Northwind renewal
10.112.122.7
Grace Whitfield
Updated
Post · Release notes — November
10.34.100.28
Ada Park
Updated
Post · Release notes — November
10.237.213.158
Ada Park
Updated
Contact · Jane Cooper
10.226.217.101
Ada Park
Signed in
API key ••••4f2a
10.5.232.223

Container-width cards

Under 640px of container width rows become cards: title, subtitle, badge, label:value meta, checkbox and actions.

Pipeline
More actions
Jane Şahin
Kestrel Robotics
Lead
$19,385.00
Leila Haddad
jane@kestrelrobot.example
Zeynep Kimura
Quarry Analytics
Lost
$87,574.00
Ada Park
zeynep@quarryanalyt.example
Freya Okafor
Kestrel Robotics
Won
$17,818.00
Selin Kaya
freya@kestrelrobot.example
Tomás Dubois
Tessel Imports
Lead
$35,947.00
Leila Haddad
tomas@tesselimport.example
Hana Costa
Marrow Capital
Proposal
$5,568.00
Ada Park
hana@marrowcapita.example

Loading, empty, no results, error

First load shows skeleton rows shaped by column type; refreshing keeps the data under a thin bar; errors keep the last rows.

Team

Custom toolbar with useDataTable

Compose the parts and drive the state yourself; table is the TanStack v9 instance.

60 contacts
Pipeline
Ethan Mehta
Ostrava Labs
Lost
$89,900.00
Clara Yıldız
Fernhill Media
Lead
$19,258.00
Zeynep Rossi
Anadolu Lojistik
Won
$46,598.00
Ethan Kimura
Halden Works
Proposal
$8,568.00
Sofia Andersen
Northwind Traders
Lead
$12,835.00
Ravi Dubois
Ridgeway Logistics
Lead
$41,764.00
Mateo Andersen
Pemberly Confectionery
Lead
$44,342.00
Yusuf Costa
Brightmoor Foods
Proposal
$85,568.00

1–8 of 60

API

DataTable

PropTypeDefault
columns
Column definitions (see below).
DataTableColumn<T>[]—
data
Rows. Client mode: every row. Server mode: the current page (or every loaded row for load-more).
T[]—
getRowId
Stable row id (selection, edits, keys).
(row: T) => string—
caption
Required accessible name. Hidden visually unless showCaption.
string—
showCaption
Show the caption above the table.
booleanfalse
mode
Server mode turns off client sorting, search, filters and paging; you load rows from onStateChange.
"client" | "server""client"
rowCount
Server mode: total matching rows.
number—
state / defaultState
Controlled (any subset) or initial q, filters, sort, page, columns, density, view.
Partial<ListState>—
onStateChange
Every change with its reason (search, filters, sort, page, pageSize, columns, density, view, reset). Search is debounced 300 ms; filter, search and sort changes reset the page.
(next, reason, requestId) => void—
urlState
Sync q, filters, sort, page, size and view (sv) with the URL. Options: prefix, keys, adapter, history, density.
boolean | UrlStateOptionsfalse
storageKey
Remember column visibility, order, widths, pins and density in localStorage.
string—
loading
Without data: skeleton rows. With data: refreshing bar, data stays.
booleanfalse
error
Danger alert above the table with a retry button; loaded rows stay.
{ message, onRetry? } | null—
pagination
Pagination bar, a Load more button, an intersection-observed sentinel (with a button fallback) or nothing.
"pages" | "load-more" | "infinite" | "none""pages"
pageSizes
Rows-per-page options; the first is the default size.
number[][25, 50, 100]
onLoadMore / hasMore
For load-more and infinite.
() => Promise<void> · boolean—
cursors
Cursor pagination: set page to { cursor, size } in state and pass the neighbouring cursors.
{ prev, next }—
virtual
Render only visible rows (TanStack Virtual). Height defaults to 32rem. A dev warning fires past 300 rendered rows without it.
boolean | { estimateRowHeight?, overscan?, height? }false
selectable
Checkbox column, Shift-click ranges, `x` toggles the focused row, Esc clears.
boolean | "single"false
selection / defaultSelection / onSelectionChange
{ mode: "ids", ids } or, for “select all” in server mode, { mode: "all", except, filters, q, total } — resets when the query changes.
DataTableSelection—
rowSelectable
A string disables the checkbox and explains why.
(row) => boolean | string—
bulkActions
Up to three inline, the rest in ⋯. confirm opens an Alert Dialog; run may return { undo }.
DataTableBulkAction<T>[]—
rowActions
⋯ menu in a pinned last column (also in cards).
(row) => MenuEntry[]—
rowHref / onRowClick
The title cell becomes a real link; the whole row is clickable by pointer.
(row) => string · (row) => void—
onCellEdit
Persist an inline edit. Reject (an Error message is shown) to roll back.
({ row, rowId, columnId, value }) => Promise<void>—
navigation
grid: WAI-ARIA data grid with arrow keys, Home/End, PageUp/PageDown, Ctrl+Home/End, Enter/F2 to edit.
"table" | "grid""table"
readOnly
Turns off inline editing.
booleanfalse
empty / noResults
No data at all vs. a search/filter that matches nothing (default has Clear filters).
ReactNode—
mobile
Under 640px of container width: cards, drop priority 3 then 2 columns, or scroll with a sticky first column.
"cards" | "priority" | "scroll""cards"
export
Adds the Export menu: page, selected or all rows as CSV (UTF-8 BOM, locale delimiter, formula guard).
{ filename, formatted?, onServerExport? }—
density
Table-local density; View › Density writes state.density.
"inherit" | "compact" | "comfortable" | "spacious""inherit"
variant
Own surface, or full width for list screens.
"card" | "flush""card"
toolbar
Extra toolbar content in the default layout.
ReactNode—
shortcuts
`/` search, Esc clear selection, `x` toggle focused row (within the table).
booleantrue
me
Resolves “Me” in user filters.
{ id, name }—
labels
Override built-in strings (dataTable namespace).
Partial<DataTableLabels>—
children
Compose DataTableToolbar, DataTableSearch, DataTableFilterBar, DataTableSavedViews, DataTableViewOptions, DataTableExport, DataTableContent, DataTableFooter yourself.
ReactNode—

DataTableColumn<T>

PropTypeDefault
id / header
Stable id and the label used in the header, column menu, CSV, cards and filter field.
string—
accessor
Value of the cell. Omit for display-only columns.
keyof T | (row) => unknown—
type
text, number, currency, percent, date, datetime, enum, boolean, user, tags — formatting, alignment, sorting, operators and editor.
FieldType"text"
cell
Custom cell ({ row, value, rowId, index, column }).
(ctx) => ReactNode—
options
enum labels, badge tones and sort order (shared with the Filter bar).
FilterOption[]—
currency
ISO 4217 for currency columns (default: the locale provider’s).
string—
sortable / sortValue
Sorting uses Intl.Collator(locale, { numeric: true }); enums sort by option order.
boolean · (row) => string | number—
searchable
Included in the global search (default: text, enum, user, tags).
boolean—
filter
Filter field overrides (quick, ops, icon, async options).
Partial<FilterFieldDef> | false—
edit
{ type: text | number | currency | date | select | boolean, options?, validate? } or { type: custom, render }.
DataTableCellEditor<T> | false—
mobile
Role in the card layout.
"title" | "subtitle" | "meta" | "badge" | "avatar" | "hidden"—
priority
mobile="priority": which columns drop first.
1 | 2 | 3—
exportValue
CSV value (default: raw value).
(row) => string | number | null—
hideable / pinnable / resizable
The title column is never hideable.
booleantrue
size / minSize / maxSize
Widths in px (defaults by type).
number—
defaultVisible
Start hidden (still available in View).
booleantrue

useDataTable()

PropTypeDefault
table
TanStack v9 instance (escape hatch).
ReactTable—
state / setState(patch, reason)
The list state and its setter.
ListState—
selection / setSelection
Current selection.
DataTableSelection—
pageRows / filteredRows / total
Rows on the page, after search + filters, and the total.
T[] · T[] · number—
filterBarProps
Spread onto a FilterBar placed anywhere inside.
FilterBarProps subset—
savedViewsBinding(views)
activeId, current, onSelect, onReset for SavedViews.
SavedViewsProps subset—
exportCsv({ scope, filename, formatted })
Download page, selected or all rows.
function—

List-state helpers

PropTypeDefault
serializeListState / parseListState
React-free; parse in a server component so the first HTML has the right rows. Unknown fields and operators are dropped.
(state, opts) ⇄ (search, opts)—
mergeListStateIntoSearch
Replace the list’s own params and keep the rest.
(search, state, opts) => string—
createHistoryAdapter / createMemoryAdapter
history.replaceState (works with Next.js useSearchParams) or in-memory.
UrlStateAdapter—
applyFilters / searchRows
The client filter semantics a server should match (between inclusive, dates by calendar day in timeZone, relative resolved at query time).
function—
toCsv / escapeCsvCell / csvDelimiter / downloadCsv
The CSV writer behind Export.
function—
filterFieldsFromColumns
Filter bar fields from table columns.
(columns) => FilterFieldDef[]—