DataTable
v1.0.29Updated 4 Sept 2026Professional sortable, filterable, groupable table powered by TanStack Table. Use for any tabular list where users need to slice the data.
Preview
Live Β· 51 storiesβΈWhy this is recommendedtablelistgridsortable+27
The default, out-of-the-box DataTable.
- βListing invoices, customers, products, employees, transactions
- βAny dataset > 25 rows where users need sort / filter / group
- βAdmin dashboards requiring bulk operations
- βReporting tables that benefit from grouping by status / category
- βSpreadsheet-like surfaces needing cell range copy or Excel export
- βTables where clicking a row expands an inline master-detail panel of related records
- Column field types (DataTableColumn.type): email/tel/url render as links (url scheme-guarded to http(s)); number/currency/date/datetime format through the app locale (useB69Locale β Thai gets Buddhist years); boolean renders an icon. Right-aligns number/currency, centers boolean. An explicit `cell` always wins; filtering/sorting stay on the raw value.
- Multi-column sorting (shift-click)
- Per-column filtering (text, number, date, enum, boolean)
- Global search (debounced, Cmd+K)
- Per-column filters (text / number / date / enum / boolean) via header popovers, OR a left filter panel (enableFilterPanel) with a Filters toolbar button β one control per filterable column, faceted enum counts + number min/max, active-filter badge. The panel scrolls independently of the table (its own overflow, so a long filter list never grows the table row) and is drag- or keyboard-resizable between 200 and 560px. In page-flow mode (maxHeight="none") it sticks to the viewport instead, so it stays on screen and scrolls on its own while the rows scroll past β set --b69-filter-panel-sticky-top to your app bar height to clear it; it publishes its live width as --b69-filter-panel-width, and reports data-state="open"/"closed" on the data-table-filter-panel slot
- Group by with per-group aggregates (sum, mean, min, max, count, unique)
- Row selection with bulk actions bar
- Icon-based row actions (getRowActions): inline quick-action icon buttons with tooltips + "β―" overflow menu; danger tone, per-row disabled
- Column visibility / pinning / resizing
- Column drag-and-drop reorder (enableColumnReorder)
- Cell range selection with copy-to-clipboard TSV (enableCellSelection)
- Row pinning sticky to top or bottom (enableRowPinning)
- Excel export with per-column formatting (enableExcelExport, meta.excelFormat)
- Pivot mode (pivot prop): spreadsheet-style matrix with rows / columns / aggregated values
- Pivot multi-level column headers + row / column / grand totals
- Excel export of the pivot matrix
- Inline cell edit
- Density toggle (compact / normal / comfortable)
- Sticky header, sticky pinned columns
- Zebra striping (striped prop) β frozen columns stripe too
- Grid line / separator control (gridLines: none | horizontal | vertical | both)
- Excel-style spreadsheet mode (spreadsheetMode): row-number gutter, active-cell keyboard nav, type-to-edit / F2, fill handle, paste-to-fill (Ctrl/Cmd+V), batched onCellsChange
- CSV export of filtered + sorted rows
- CSV import via toolbar (enableImport + onImport)
- MasterDetail expandable row content (masterDetail render prop)
- View presets (save / restore filter + sort + group + visibility)
- Keyboard navigation
- Loading / empty / error states
- Sub-rows / expandable hierarchy
- i18n: labels prop overrides every built-in UI string (toolbar, pagination, selection/bulk bar, empty/error states, aria-labels); counts format through LocaleProvider (Thai numerals, Buddhist calendar apps)
- βSmall lists (< 10 rows β use Table or KVList)
- βA simple, plain, static table with no interactivity (use Table)
- βHierarchical tree data primary (use FileBrowser)
- βCard-based gallery layouts (use a grid of Cards)
Install
Ask your agent, or call the MCP tool directly. The resolved bundle is written into your project.
Usage
Tick variants in the left menu β each adds a tab with that variantβs real story source, the same code the preview above renders.
Props
dataArray of row objects
yescolumnsColumn definitions
yesrowSelectionEnable row selection
βbulkActionsSelection toolbar actions
βgetRowActionsPer-row actions column. Mark an action `inline: true` (with an icon) to render it as an icon button with its label as tooltip (visible by default β see `rowActionsReveal`); the rest collect in a "β―" overflow menu. `tone: 'danger'` for destructive styling, `disabled` grays out without hiding. Keep 2β3 inline, long tail in the menu.
βrowActionsRevealWhen the row-action icons show. 'always' (default) keeps them on screen β most discoverable, and the only option that works on touch. 'hover' reveals them on row hover / keyboard focus / open menu (denser Linear/Attio pattern, but invisible on touch).
βviewsSaved view presets
βstateTop-level UI state
βonRowClickRow click handler
βenableFilterPanelShow a Filters button in the toolbar that toggles a left-hand filter panel, wired to the same column filters as the header popovers. Off by default. PREREQUISITE: the panel lists one control per column that declares `meta.filterType` ('text'|'number'|'date'|'enum'|'boolean') AND has a resolvable value (an accessorKey/accessorFn or matching id) β columns without a filterType are skipped, so enabling the prop on a table whose columns declare none renders an empty panel. Give each filterable column a meta.filterType.
βfilterPanelModePlacement of the filter panel (needs enableFilterPanel). 'rail' (default) is an inline sibling that takes 256px when open β best for a full-width table. 'overlay' floats the panel over the table's left edge with a dismiss backdrop so the table keeps its full width β use inside Sheets/drawers/narrow panes where a rail would crush the columns. 'auto' is rail on wide containers, overlay once the container narrows (<560px). Default 'rail' is byte-identical. MCP feedback fb_903212e0.
βfilterPanelResizableLet the user drag the filter panel's right edge to resize it. On by default β the handle is a transparent 6px strip that only paints on hover/focus, and the panel still opens at defaultFilterPanelWidth, so nothing moves until it is dragged. Keyboard-resizable too: the handle is a focusable ARIA separator (arrows nudge 8px, Shift+arrow 32px, Home/End snap to the bounds). Set false for a fixed-width panel.
βdefaultFilterPanelWidthWidth (px) the filter panel opens at. Default 256 β the previous fixed width, so an untouched panel is unchanged.
βfilterPanelWidthControlled filter-panel width (px). Pass with onFilterPanelWidthChange to own the width β e.g. to persist it per user. Clamped to filterPanelMinWidth/filterPanelMaxWidth on read, so a stored value can never render the panel out of bounds.
βonFilterPanelWidthChangeFires on every resize step with the new clamped width (px). Use with filterPanelWidth for a controlled panel, or alone to persist an uncontrolled one.
βfilterPanelMinWidthLower resize bound (px). Default 200 β below this the enum option rows wrap badly.
βfilterPanelMaxWidthUpper resize bound (px). Default 560 β above this the panel crowds out the table it filters.
βcolumns[].meta.headerHelpRenders a "?" beside the column header explaining what the column means. A node gives a one-liner; { title, body } gives a heading plus detail. Composing this yourself needs a function `header`, which loses the plain-string header ergonomics (filter chips, the column picker and export all read `label`) and repeats on every column. The trigger is deliberately a SIBLING of the sort control, never a child: a button inside the sort button is invalid HTML and a React 19 SSR hydration error. On a column that opts in, the sort hit area shrinks from the whole header cell to the label. MCP feedback fb_54569a6f.
βpersistFilterPanelWidthKeylocalStorage key that persists the UNCONTROLLED panel width across sessions. Ignored when filterPanelWidth is controlled. The stored value is adopted after mount (never during render), so it cannot cause a hydration mismatch. Best-effort β a storage failure in private mode is swallowed. MCP feedback fb_c860aaf0.
βdefaultFilterPanelOpenWhether the filter panel starts open. Default false (unchanged).
βfilterPanelOpenControlled filter-panel open state. Pass with onFilterPanelOpenChange to own it β e.g. to restore whether the user left the panel open on their last visit. MCP feedback fb_4c48a1f5.
βonFilterPanelOpenChangeFires whenever the panel opens or closes β the toolbar button, the panel close button, or the overlay dismiss backdrop. Use with filterPanelOpen for a controlled panel, or alone to persist an uncontrolled one.
βenableColumnReorderDrag-and-drop column reorder
βenableCellSelectionCell range selection + copy TSV
βenableRowPinningPin rows to top / bottom
βenableExcelExportExcel export action (uses meta.excelFormat)
βstripedAlternate row background (zebra striping); frozen columns stripe too
βgridLinesSeparator display (default 'horizontal' = row dividers only)
βlayoutColumn width strategy. 'fit' fills the container β flexible columns (no explicit size, unpinned, not user-resized) share the remaining width so the table never overflows or clips a trailing column; explicit-size / system / pinned columns keep their widths. 'auto' (default) lets the table scroll when columns exceed the viewport.
βenableImportShow a toolbar CSV Import button
βonImportReceives parsed CSV records (keyed by header) on import
βspreadsheetModeExcel-style mode: row-number gutter, active-cell keyboard nav, type-to-edit/F2, fill handle, paste-to-fill (implies cell selection; editing needs editable columns)
βonCellsChangeBatched change callback for spreadsheet fill/paste/edits (falls back to onCellEdit per cell)
βmasterDetailRender expandable detail content per row
βfilteredEmptyStateShown when active filters/search reduce a NON-empty dataset to zero rows (defaults to a "no matching rows" message). Distinct from emptyState β which is only shown when the dataset is genuinely empty β so a "create your first record" prompt never renders on a filtered-to-zero table.
βpivotRender a spreadsheet-style pivot matrix from the data (multi-level column headers + row / column / grand totals + Excel export)
βlabelsOverride any built-in UI string β toolbar (search placeholder, density, Columns/Views/Group, Export), pagination ('Showing {range} of {total}', '{count} / page', '{page} / {pageCount}'), selection ('{count} selected', select-all/select-row aria), empty/error states, filter-panel controls (operator options, value/min/max placeholders, option search, boolean Either/Yes/No, date-range prompt), row-actions and sort/pin/resize aria-labels. Strings with {token} placeholders are templates. Defaults: DEFAULT_DATA_TABLE_LABELS (English, exported). Wrap the app in LocaleProvider to also format all counts per locale (e.g. Thai numerals).
β