Table
Overview
A data table component for displaying row and column data. STable combines the Aria TableCompact aggregation component (9 zero-style base primitives in the TableRoot family) with the tableVariants style recipe (29 slots, 7 sizes × 2 visual variants). Supports a config-driven columns model (grouped headers, index/selection/expand type columns), sorting, filtering, multi/single selection, expandable and tree rows, fixed columns/headers, drag-and-keyboard column resizing, virtualization, and empty states; default/simple variants plus bordered/rounded/striped toggles. All interaction text and aria-labels (sorting, filtering, selection, expansion, column resize) are localized via useLocaleMessages.
Usage
Features
- 📋 Config-driven columns — TanStack-first
columns: TableColumn<T>[](accessorKey/header/size/minSize+ VeanUIalign/type/fixed/hiddenextensions), grouped headers (columns),index/selection/expandtype columns;rowKeykeeps row identity stable - 🔀 Sorting —
enableSorting: true(orsortFncomparator) on a column; controlledsorting/v-model:sorting(TanStackSortingState);aria-sort+ localized sort-buttonaria-label - 🔍 Filtering —
enableColumnFilter: true(+filterPlaceholder/filterOptions); compound{ keyword, values }filter values, keyword search, option multi-select, summary count, and clear action are all localized; state is TanStackColumnFiltersState(v-model:columnFilters) - ✅ Selection — multi-select by default (
multipledefaults totrue, checkboxes + header select-all);multiple={false}switches to single-select (row radio); controlledselected/v-model:selected - 🧩 Expandable & tree rows —
expandtype column or theexpanded-rowslot renders expanded rows;children/getChildrendrive tree rows (enginegetSubRows);expandedfollows TanStackExpandedState - 📐 Fixed & resizable —
fixed: 'start' | 'end'seeds TanStack column pinning with gradient shadow indicators;resizablecolumns support pointer drag and arrow-key resizing writing TanStackcolumnSizing(localizedaria-label) - ⚡ Performance —
virtual+heightenable virtualization (customestimateSize/virtualizerOptions), rendering only visible rows - 🎛️ Engine escape hatch — slot props carry
table/engineColumnengine instances,useTableEngine()injects the engine anywhere inside the table subtree, the template ref exposestable, and thetableOptionsprop passes raw TanStack options through (pagination, row selection, manual modes, meta,initialState…) - 🌐 Localized by default — empty state, sort/filter/select/expand/resize
aria-labels, and the filter popover copy all useuseLocaleMessages(21table.*messages across 14 language packs)
Component family
STable(styled) — entry wrapper;tableVariantsrecipe (29 slots = 16 Aria slots + 11 filter/selection extra slots + 2 internal radio slots);useOmitPropsforwarding +useForwardListenersevent merging + full slot passthrough; injects defaultheader-selection/selection/header-sort/header-filter/header-resize/tree-toggle/expand/emptyslot contentTableCompact(Aria) — aggregation state owner over the@tanstack/vue-tableengine:useTableCompactState(sorting/columnFilters/expanded/columnPinning/columnSizingviauseControllableState,selected/multipleviauseSelection),useTableCompactTable(engine instance wiring),useTableCompactData(header groups/leaf columns/row model),useTableCompactResize(pointer + keyboard column widths),useTableCompactVirtual;provideTableCompactContextbridges the 9 primitivesTableRoot(Aria) — root element,dirdirection, rendersdata-vean-table-rootand the table semantic containerTableScroll/TableContent/TableHeader/TableBody/TableFooter/TableRow/TableHead/TableCell(Aria) — 9 base primitives, all zero-style, each renderingdata-vean-table-*data attributesTableCompactHead/TableCompactRow/TableCompactCell/TableCompactExpandedRow/TableVirtualSpacerRow(Aria internal) — composition and rendering components inside the Compact aggregation (not publicly exported)STableFilterPopover(styled internal) — the filter popover (SPopover + SInput + SCheckbox + SButton) with localized search/options/summary/clearSTableRadio(styled internal) — the row radio used in single-select mode (aria-pressedsemantics), consuming theradioRoot/radioIndicatorrecipe slots directlyuseTable/usePaginatedTable(UI hooks) — client-side sort/filter/pagination composition hooks
Demos
Basic
| # | Name | Age | Header Address | Details |
|---|---|---|---|---|
| 1 | John Doe | 30 | 123 Main St | |
| 2 | Jane Smith | 25 | 456 Elm St | |
| 3 | Bob Johnson | 40 | 789 Oak St | |
| 4 | Alice Brown | 35 | 321 Pine St | |
| 5 | Charlie Davis | 28 | 654 Maple | Detail for Charlie Davis |
| 6 | Eve White | 32 | 987 Cedar St | |
| 7 | Frank Green | 45 | 246 Birch St | |
| 8 | Grace Lee | 27 | 135 Spruce St | |
| 9 | Hank Miller | 38 | 864 Willow St | |
| 10 | Ivy Wilson | 29 | 753 Aspen St |
Empty
| # | Name | Age | Address |
|---|---|---|---|
No dataThere is no data to display. | |||
| # | Name | Age | Address |
|---|---|---|---|
No matching recordsTry adjusting filters or create the first record from here. | |||
Grouped
| Profile | Metrics | ||
|---|---|---|---|
| Name | City | Age | Score |
| Ada Lovelace | London | 36 | 96 |
| Linus Torvalds | Helsinki | 28 | 88 |
| Grace Hopper | New York | 41 | 92 |
Sorting
| Name | Age | Score |
|---|---|---|
| Ada Lovelace | 36 | 96 |
| Linus Torvalds | 28 | 88 |
| Grace Hopper | 41 | 92 |
Filtering
| Name | City | Score |
|---|---|---|
| Ada Lovelace | London | 96 |
| Linus Torvalds | Helsinki | 88 |
| Grace Hopper | New York | 92 |
Fixed
Two columns stay pinned on the left and the score stays pinned on the right while the table scrolls horizontally.
| Employee ID | Name | Role | Team | Status | Location | Timezone | Phone | Projects | Utilization | Last Updated | Score | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ENG-001 | Ada Lovelace | Architect | Core Platform | Active | London | UTC+0 | [email protected] | +44 20 7946 001 | 4 | 91% | 2026-04-10 | 96 |
| ENG-014 | Linus Torvalds | Maintainer | Kernel Systems | Reviewing | Helsinki | UTC+2 | [email protected] | +358 9 1234 014 | 6 | 84% | 2026-04-12 | 88 |
| ENG-021 | Grace Hopper | Scientist | Developer Experience | Active | New York | UTC-4 | [email protected] | +1 212 555 0021 | 5 | 89% | 2026-04-14 | 92 |
| ENG-037 | Margaret Hamilton | Tech Lead | Design Systems | Planning | Boston | UTC-4 | [email protected] | +1 617 555 0037 | 3 | 76% | 2026-04-09 | 94 |
| ENG-052 | Katherine Johnson | Analyst | Data Intelligence | Active | Hampton | UTC-4 | [email protected] | +1 757 555 0052 | 7 | 93% | 2026-04-13 | 98 |
Resizable
Drag any header handle to adjust the column width.
| Employee ID | Name | Role | Team | Status | Location | Timezone | Phone | Projects | Utilization | Last Updated | Score | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ENG-001 | Ada Lovelace | Architect | Core Platform | Active | London | UTC+0 | [email protected] | +44 20 7946 001 | 4 | 91% | 2026-04-10 | 96 |
| ENG-014 | Linus Torvalds | Maintainer | Kernel Systems | Reviewing | Helsinki | UTC+2 | [email protected] | +358 9 1234 014 | 6 | 84% | 2026-04-12 | 88 |
| ENG-021 | Grace Hopper | Scientist | Developer Experience | Active | New York | UTC-4 | [email protected] | +1 212 555 0021 | 5 | 89% | 2026-04-14 | 92 |
| ENG-037 | Margaret Hamilton | Tech Lead | Design Systems | Planning | Boston | UTC-4 | [email protected] | +1 617 555 0037 | 3 | 76% | 2026-04-09 | 94 |
| ENG-052 | Katherine Johnson | Analyst | Data Intelligence | Active | Hampton | UTC-4 | [email protected] | +1 757 555 0052 | 7 | 93% | 2026-04-13 | 98 |
Tree
| Name | Age | Role |
|---|---|---|
Engineering | 12 | Department |
Platform Team | 6 | Team |
Design Team | 4 | Team |
Operations | 8 | Department |
Virtualized
| # | Name | Age | Address |
|---|---|---|---|
| 1 | User 1 | 20 | Block 1, Soybean Street |
| 2 | User 2 | 21 | Block 2, Soybean Street |
| 3 | User 3 | 22 | Block 3, Soybean Street |
| 4 | User 4 | 23 | Block 4, Soybean Street |
| 5 | User 5 | 24 | Block 5, Soybean Street |
| 6 | User 6 | 25 | Block 6, Soybean Street |
| 7 | User 7 | 26 | Block 7, Soybean Street |
| 8 | User 8 | 27 | Block 8, Soybean Street |
| 9 | User 9 | 28 | Block 9, Soybean Street |
| 10 | User 10 | 29 | Block 10, Soybean Street |
| 11 | User 11 | 30 | Block 11, Soybean Street |
| 12 | User 12 | 31 | Block 12, Soybean Street |
| 13 | User 13 | 32 | Block 13, Soybean Street |
| 14 | User 14 | 33 | Block 14, Soybean Street |
| 15 | User 15 | 34 | Block 15, Soybean Street |
| 16 | User 16 | 35 | Block 16, Soybean Street |
Expandable
| Name | Age | Address | |
|---|---|---|---|
| John Doe | 30 | 123 Main St | |
| Jane Smith | 25 | 456 Elm St | |
| Bob Johnson | 40 | 789 Oak St |
Footer
| # | Name | Age | Address |
|---|---|---|---|
| 1 | John Doe | 30 | 123 Main St |
| 2 | Jane Smith | 25 | 456 Elm St |
| 3 | Bob Johnson | 40 | 789 Oak St |
Total: 3 items | |||
Bottom
| # | Name | Age | Address |
|---|---|---|---|
| 1 | John Doe | 30 | 123 Main St |
| 2 | Jane Smith | 25 | 456 Elm St |
| 3 | Bob Johnson | 40 | 789 Oak St |
Multiple Selection
| Name | Age | Address | |
|---|---|---|---|
| John Doe | 30 | 123 Main St | |
| Jane Smith | 25 | 456 Elm St | |
| Bob Johnson | 40 | 789 Oak St |
Single Selection
| Name | Age | Address | |
|---|---|---|---|
| John Doe | 30 | 123 Main St | |
| Jane Smith | 25 | 456 Elm St | |
| Bob Johnson | 40 | 789 Oak St |
Row Events
| Name | Role | Region |
|---|---|---|
| Ada Lovelace | Architect | London |
| Linus Torvalds | Maintainer | Helsinki |
| Grace Hopper | Compiler Lead | New York |
Latest row event
Click, double click, or right click a row to inspect the emitted payload.
- rowKey
- -
- index
- -
- level
- -
rowData
{
"rowData": null
}rowMouseenter and rowMouseleave use the same payload shape when you need hover-driven behavior.
- 01 Basic — plain
columns+datarendering - 02 Variant —
default/simplevisual variants - 03 Bordered —
borderedtoggle - 04 Rounded —
roundedtoggle - 05 Striped —
stripedzebra rows - 06 Empty — empty state (default
SEmptyand customemptyslot) - 07 Grouped — grouped headers (
columns) - 08 Sorting —
enableSorting+sortFnwith controlled/uncontrolled sorting - 09 Filtering —
enableColumnFilteroptions and the keyword filter popover - 10 Fixed —
fixed: 'start' | 'end'fixed columns - 11 Resizable —
resizabledrag/keyboard column resize - 12 Tree — tree rows (
childrendata +tree-toggle) - 13 Virtualized —
virtual+heightvirtualization - 14 Expandable —
expandcolumn andexpanded-rowslot - 15 Footer —
footersummary slot - 16 Bottom —
bottomslot - 17 Multiple Selection — multi-select (checkboxes + select-all)
- 18 Single Selection —
multiple={false}single-select (radio) - 19 Row Events —
rowClick/rowDblclick/rowContextmenurow events - 20 Sizes — 7 sizes (xs–2xl)
API
Aria Layer
29Table<T, R, M>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | ClassValue | — | Additional class names to apply to the table. |
size | ThemeSize | — | Visual size of the component. |
ui | Partial<TableExtendedUi> | — | Per-slot class overrides for the component. |
variant | TableVariant | — | Visual variant of the component. |
bordered | boolean | — | Whether bordered. |
rounded | boolean | — | Whether rounded. |
striped | boolean | — | Whether striped. |
columns | TableColumn<T>[] | — | Column definitions. TanStack column defs are the first citizens. |
data | T[] | — | Data. |
rowKey | (row: T) => R | — | Row key. |
getChildren | ((row: T) => T[]) | — | Nested children resolver used as the engine `getSubRows`. |
sorting | SortingState | — | Sorting state (TanStack `SortingState`). |
defaultSorting | SortingState | — | Default sorting state. |
tableOptions | TableEngineOptions | — | Engine options passed through to the TanStack `useTable` call. Keys the
component owns (data, columns, state wiring, owned change handlers) are
overridden by the component; everything else unlocks the full engine
surface (pagination, row selection, manual modes, meta, initialState...). |
columnFilters | ColumnFiltersState | — | Column filters state (TanStack `ColumnFiltersState`). Filter values accept
the compound `{ keyword, values }` shape handled by the built-in filter. |
defaultColumnFilters | ColumnFiltersState | — | Default column filters state. |
expanded | ExpandedState | — | Expanded state (TanStack `ExpandedState`). |
defaultExpanded | ExpandedState | — | Default expanded state. |
defaultExpandAll | boolean | — | Whether to expand all rows by default. |
columnPinning | ColumnPinningState | — | Column pinning state (TanStack `ColumnPinningState`). Column `fixed`
fields seed the default state when this prop is absent. |
defaultColumnPinning | ColumnPinningState | — | Default column pinning state. |
columnSizing | ColumnSizingState | — | Column sizing state (TanStack `ColumnSizingState`, px keyed by column id). |
defaultColumnSizing | ColumnSizingState | — | Default column sizing state. |
indent | number | — | Indent width applied to nested items. |
virtual | boolean | — | Whether virtual. |
height | string | number | — | Height. |
estimateSize | number | ((index: number, row: T) => number) | — | Estimate size. |
virtualizerOptions | VirtualizerOptions | — | Virtualizer options. |
contentProps | TableContentProps | — | Properties forwarded to the content element. |
headerProps | TableHeaderProps | — | Properties forwarded to the header element. |
bodyProps | TableBodyProps | — | Properties forwarded to the body element. |
footerProps | TableFooterProps | — | Properties forwarded to the footer element. |
headProps | TableHeadProps | — | Properties forwarded to the head element. |
rowProps | TableRowProps | — | Properties forwarded to the row element. |
cellProps | TableCellProps | — | Properties forwarded to the cell element. |
dir | Direction | — | Reading direction of the component. |
defaultSelected | (M extends true ? R[] : R) | — | Default selected. |
selected | (M extends true ? R[] : R) | — | Whether the item is selected. |
multiple | M | — | Whether multiple values are supported. |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
update:sorting | SortingState | Emitted when the sorting state value changes. |
update:columnFilters | ColumnFiltersState | Emitted when the column filters state value changes. |
update:expanded | ExpandedState | Emitted when the expanded state value changes. |
update:columnPinning | ColumnPinningState | Emitted when the column pinning state value changes. |
update:columnSizing | ColumnSizingState | Emitted when the column sizing state value changes. |
update:selected | M extends true ? R[] : R | undefined | Emitted when the selected state changes. |
rowClick | MouseEvent, payload: TableRowEventPayload<T, R> | Emitted when a row is clicked. |
rowDblclick | MouseEvent, payload: TableRowEventPayload<T, R> | Emitted when a row is double clicked. |
rowContextmenu | MouseEvent, payload: TableRowEventPayload<T, R> | Emitted when a row context menu is triggered. |
rowMouseenter | MouseEvent, payload: TableRowEventPayload<T, R> | Emitted when the pointer enters a row. |
rowMouseleave | MouseEvent, payload: TableRowEventPayload<T, R> | Emitted when the pointer leaves a row. |
Slots
| Slot Name | Parameters | Description |
|---|---|---|
bottom | — | Custom content for the bottom slot. |
header | TableHeaderSlotProps<T> | Custom content for the header slot. |
header-index | { column: TableColumn<T>; } | Custom content for the header index slot. |
header-selection | TableHeaderSelectionSlotProps<T> | Custom content for the header selection slot. |
header-expand | { column: TableColumn<T>; } | Custom content for the header expand slot. |
header-filter | TableHeaderFilterSlotProps<T> | Custom content for the header filter slot. |
header-sort | TableHeaderSortSlotProps<T> | Custom content for the header sort slot. |
header-resize | TableHeaderResizeSlotProps<T> | Custom content for the header resize slot. |
index | TableIndexSlotProps<T> | Custom content for the index slot. |
selection | TableSelectionSlotProps<T> | Custom content for the selection slot. |
expand | TableExpandSlotProps<T> | Custom content for the expand slot. |
expanded-row | TableExpandedRowSlotProps<T> | Custom content for the expanded row slot. |
tree-toggle | TableTreeToggleSlotProps<T> | Custom content for the tree toggle slot. |
empty | TableEmptySlotProps | Custom content for the empty slot. |
footer | { columnSize: number; } | Custom content for the footer slot. |
TableBody
TableCell
TableCompact<T, R, M>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
columns | TableColumn<T>[] | — | Column definitions. TanStack column defs are the first citizens. |
data | T[] | — | Data. |
rowKey | (row: T) => R | — | Row key. |
getChildren | ((row: T) => T[]) | — | Nested children resolver used as the engine `getSubRows`. |
sorting | SortingState | — | Sorting state (TanStack `SortingState`). |
defaultSorting | SortingState | — | Default sorting state. |
tableOptions | TableEngineOptions | — | Engine options passed through to the TanStack `useTable` call. Keys the
component owns (data, columns, state wiring, owned change handlers) are
overridden by the component; everything else unlocks the full engine
surface (pagination, row selection, manual modes, meta, initialState...). |
columnFilters | ColumnFiltersState | — | Column filters state (TanStack `ColumnFiltersState`). Filter values accept
the compound `{ keyword, values }` shape handled by the built-in filter. |
defaultColumnFilters | ColumnFiltersState | — | Default column filters state. |
expanded | ExpandedState | — | Expanded state (TanStack `ExpandedState`). |
defaultExpanded | ExpandedState | — | Default expanded state. |
defaultExpandAll | boolean | — | Whether to expand all rows by default. |
columnPinning | ColumnPinningState | — | Column pinning state (TanStack `ColumnPinningState`). Column `fixed`
fields seed the default state when this prop is absent. |
defaultColumnPinning | ColumnPinningState | — | Default column pinning state. |
columnSizing | ColumnSizingState | — | Column sizing state (TanStack `ColumnSizingState`, px keyed by column id). |
defaultColumnSizing | ColumnSizingState | — | Default column sizing state. |
indent | number | — | Indent width applied to nested items. |
virtual | boolean | — | Whether virtual. |
height | string | number | — | Height. |
estimateSize | number | ((index: number, row: T) => number) | — | Estimate size. |
virtualizerOptions | VirtualizerOptions | — | Virtualizer options. |
contentProps | TableContentProps | — | Properties forwarded to the content element. |
headerProps | TableHeaderProps | — | Properties forwarded to the header element. |
bodyProps | TableBodyProps | — | Properties forwarded to the body element. |
footerProps | TableFooterProps | — | Properties forwarded to the footer element. |
headProps | TableHeadProps | — | Properties forwarded to the head element. |
rowProps | TableRowProps | — | Properties forwarded to the row element. |
cellProps | TableCellProps | — | Properties forwarded to the cell element. |
dir | Direction | — | Reading direction of the component. |
defaultSelected | (M extends true ? R[] : R) | — | Default selected. |
selected | (M extends true ? R[] : R) | — | Whether the item is selected. |
multiple | M | — | Whether multiple values are supported. |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
update:sorting | SortingState | Emitted when the sorting state value changes. |
update:columnFilters | ColumnFiltersState | Emitted when the column filters state value changes. |
update:expanded | ExpandedState | Emitted when the expanded state value changes. |
update:columnPinning | ColumnPinningState | Emitted when the column pinning state value changes. |
update:columnSizing | ColumnSizingState | Emitted when the column sizing state value changes. |
update:selected | M extends true ? R[] : R | undefined | Emitted when the selected state changes. |
rowClick | MouseEvent, payload: TableRowEventPayload<T, R> | Emitted when a row is clicked. |
rowDblclick | MouseEvent, payload: TableRowEventPayload<T, R> | Emitted when a row is double clicked. |
rowContextmenu | MouseEvent, payload: TableRowEventPayload<T, R> | Emitted when a row context menu is triggered. |
rowMouseenter | MouseEvent, payload: TableRowEventPayload<T, R> | Emitted when the pointer enters a row. |
rowMouseleave | MouseEvent, payload: TableRowEventPayload<T, R> | Emitted when the pointer leaves a row. |
Slots
| Slot Name | Parameters | Description |
|---|---|---|
bottom | — | Custom content for the bottom slot. |
header | TableHeaderSlotProps<T> | Custom content for the header slot. |
header-index | { column: TableColumn<T>; } | Custom content for the header index slot. |
header-selection | TableHeaderSelectionSlotProps<T> | Custom content for the header selection slot. |
header-expand | { column: TableColumn<T>; } | Custom content for the header expand slot. |
header-filter | TableHeaderFilterSlotProps<T> | Custom content for the header filter slot. |
header-sort | TableHeaderSortSlotProps<T> | Custom content for the header sort slot. |
header-resize | TableHeaderResizeSlotProps<T> | Custom content for the header resize slot. |
index | TableIndexSlotProps<T> | Custom content for the index slot. |
selection | TableSelectionSlotProps<T> | Custom content for the selection slot. |
expand | TableExpandSlotProps<T> | Custom content for the expand slot. |
expanded-row | TableExpandedRowSlotProps<T> | Custom content for the expanded row slot. |
tree-toggle | TableTreeToggleSlotProps<T> | Custom content for the tree toggle slot. |
empty | TableEmptySlotProps | Custom content for the empty slot. |
footer | { columnSize: number; } | Custom content for the footer slot. |
TableCompactCell
Props
| Prop | Type | Default | Description |
|---|---|---|---|
column | TableColumn<T extends TableBaseData = TableBaseData> | — | Column definition. |
row | TableTreeRow<T extends TableBaseData = TableBaseData, R extends TableUnifiedKey = TableUnifiedKey> | — | Row. |
index | number | — | Index of the current item. |
Slots
| Slot Name | Parameters | Description |
|---|---|---|
index | TableIndexSlotProps<T> | Custom content for the index slot. |
selection | TableSelectionSlotProps<T> | Custom content for the selection slot. |
expand | TableExpandSlotProps<T> | Custom content for the expand slot. |
tree-toggle | TableTreeToggleSlotProps<T> | Custom content for the tree toggle slot. |
TableCompactExpandedRow
Props
| Prop | Type | Default | Description |
|---|---|---|---|
row | TableTreeRow<T extends TableBaseData = TableBaseData, R extends TableUnifiedKey = TableUnifiedKey> | — | Row. |
index | number | — | Index of the current item. |
colspan | number | — | Colspan. |
TableCompactHead
Props
| Prop | Type | Default | Description |
|---|---|---|---|
header | TableEngineHeader<T extends TableBaseData = TableBaseData> | — | Engine header instance. |
TableCompactRow
Props
| Prop | Type | Default | Description |
|---|---|---|---|
row | TableTreeRow<T extends TableBaseData = TableBaseData, R extends TableUnifiedKey = TableUnifiedKey> | — | Row. |
index | number | — | Index of the current item. |
leafColumns | TableColumn<TableBaseData>[] | — | Leaf columns. |
TableContent
TableDataCell
TableEmpty
TableExpand
TableExpandedRow
TableFilterPopover<T>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
size | ThemeSize | — | Visual size of the component. |
ui | TableExtendedUi | — | Per-slot class overrides for the component. |
column | TableColumn<T extends TableBaseData = TableBaseData> | — | Column definition exposed in the slot scope. |
filterValue | string | — | Filter value exposed in the slot scope. |
filterValues | string[] | — | Filter values exposed in the slot scope. |
filterState | TableFilterValue | — | Filter state exposed in the slot scope. |
filtered | boolean | — | Whether filtered. |
filterOptions | TableColumnFilterOption[] | — | Filter options exposed in the slot scope. |
setFilterValue | (value: string) => void | — | Set filter value exposed in the slot scope. |
setFilterValues | (values: string[]) => void | — | Set filter values exposed in the slot scope. |
setFilterState | (value: TableFilterValue | undefined) => void | — | Set filter state exposed in the slot scope. |
toggleFilterOption | (value: string) => void | — | Toggle filter option exposed in the slot scope. |
isFilterOptionSelected | (value: string) => boolean | — | Whether the filter option is selected. |
clearFilter | () => void | — | Clear filter exposed in the slot scope. |
TableFooter
TableHead
TableHeader
TableHeaderFilter
TableHeaderResize
TableHeaderSelection
TableHeaderSort
TableIndex
TableRadio
Props
| Prop | Type | Default | Description |
|---|---|---|---|
size | ThemeSize | — | Visual size of the component. |
checked | boolean | — | Whether the item is checked. |
TableRoot
Props
| Prop | Type | Default | Description |
|---|---|---|---|
dir | Direction | — | Reading direction of the component. |
TableRow
TableScroll
TableSelection<R, M>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
defaultSelected | (M extends true ? R[] : R) | — | Default selected. |
selected | (M extends true ? R[] : R) | — | Whether the item is selected. |
multiple | M | — | Whether multiple values are supported. |
TableTreeToggle
TableVirtualSpacerRow
Props
| Prop | Type | Default | Description |
|---|---|---|---|
isVirtual | boolean | — | Whether virtual scrolling is enabled. |
colspan | number | — | Number of columns the spacer spans. |
paddingStart | number | — | Padding applied before the start of the row. |
paddingEnd | number | — | Padding applied after the end of the row. |
Notes
Architecture and benchmark differences
The table engine is @tanstack/vue-table (v9): column defs, sorting, filtering, expanding, pinning, and sizing follow TanStack state contracts (SortingState, ColumnFiltersState, ExpandedState, ColumnPinningState, ColumnSizingState), and all of them are controlled/uncontrolled dual channels via useControllableState + useSelection; all base primitives stay zero-style and only the UI wrapper injects tableVariants classes. The filter popover and single-select radio are UI-internal components consumed by default slots, but consumers can replace them entirely via same-name slots (header-filter/selection, etc.). Sort buttons and filter triggers are absolutely-positioned icon buttons with localized aria-labels; column resizing supports both pointer (PointerEvent) and keyboard (arrow-key) channels. Virtualization uses the built-in @vean/aria virtualizer, rendering only visible rows while syncing measured column widths. Ant Design / Element Plus tables are declarative component instances (el-table-column) whose fixed columns rely on config classes; Vean’s controlled state, aria-sort semantics, and full-chain localization (including the filter popover) exceed most mainstream libraries.
| Capability | VeanUI | Ant Design | Element Plus | Naive UI | Mantine Table |
|---|---|---|---|---|---|
| Aria/styled split | ✅ | — | — | — | — |
| Config columns + grouped headers | ✅ | ✅ | ✅ | ✅ | ✅ |
| Sorting (aria-sort + controlled) | ✅ | ✅ | ✅ | ✅ | ⚠️ |
| Filtering (keyword + multi-select) | ✅ | ✅ | ✅ | ✅ | ⚠️ |
| Selection (multi/single + select-all) | ✅ | ✅ | ✅ | ✅ | ⚠️ |
| Expandable + tree rows | ✅ | ✅ | ✅ | ✅ | ⚠️ |
| Fixed columns / fixed header | ✅ | ✅ | ✅ | ✅ | ⚠️ |
| Drag + keyboard column resize | ✅ | ✅ | ⚠️ | ✅ | — |
| Virtualization | ✅ | ✅ | ⚠️ | ✅ | ⚠️ |
⚠️ = partial support (Mantine Table’s sorting/filtering/selection are manual; Element Plus virtualization only exists in the separate el-table-v2; Element Plus drag-resize requires border mode).
Cautions
- Columns are TanStack
ColumnDefs first: data columns useaccessorKey(oraccessorFn),headerfor the label,size/minSizefor pixel widths. The legacydataIndex/titlefields have been removed — rename them toaccessorKey/header. - Sorting, filtering, and resizing are opt-in per column:
enableSorting,enableColumnFilter,resizable(all defaultfalse). - Group columns nest via
columns(TanStack shape); tree rows nest via the rowchildrenfield orgetChildren— these are different concepts and different fields. multipledefaults totrue(multi-select); setting it tofalserenders row radios, makesselecteda single value (R | undefined), and hides the header select-all checkbox.- Sorting/filtering/expansion/pinning/sizing are all controlled/uncontrolled dual channels: when passing
sorting(etc.) you must also listen for the matchingupdate:sorting(or usev-model:sorting) to write back, otherwise state does not update. - The controlled
expandedstate is a TanStackExpandedStatekeyed byString(rowKey(row))({ '1': true }, ortruefor all);defaultExpandAllonly applies when expansion is uncontrolled. - While a column filter is active, tree rows are force-expanded so filtered ancestors stay visible.
- Virtualization requires
height; without itvirtualis ignored (falls back to normal rendering). - Fixed columns inject a background on
data-fixedcells; fixed-column shadows are drawn via thedata-fixed-last-start/data-fixed-first-enddata attributes — keep these attributes when customizingcell/rowslots to preserve the visuals. - Filter popover copy (summary, keyword, options, clear) and the empty state follow the
ConfigProviderlocale; the column label falls back throughheader→id/accessorKey.
FAQ
How do I select rows and read the selected data?
Use multiple (multi-select by default) with v-model:selected and rowKey:
<STable :columns="columns" :data="data" :row-key="row => row.id" v-model:selected="selected" />selected is an array of the selected rows’ rowKeys; map them back to data when you need the full row objects.
How do I make a single-select table?
Set multiple={false} — row radios render and selected becomes a single value:
<STable :columns="columns" :data="data" :row-key="row => row.id" :multiple="false" v-model:selected="selected" />How do I customize the filter popover?
Replace the default STableFilterPopover entirely with the header-filter slot, or configure filter options and keyword matching on the column:
<STable :columns="columns" :data="data" :row-key="row => row.id">
<template #header-filter="{ column, filtered, filterValues, setFilterValues, clearFilter }">
<!-- custom filter control -->
</template>
</STable>How do I set the initial sort/filter state?
Seed the uncontrolled defaultSorting/defaultColumnFilters, then let the component maintain state internally; switch to the controlled channels and listen for update:sorting/update:columnFilters when you need external sync:
<STable :columns="columns" :data="data" :row-key="row => row.id" :default-sorting="[{ id: 'age', desc: false }]" />How do I handle large datasets?
Enable virtualization (virtual + height) and tune estimateSize as needed; the engine row models are memoized, so 1000-row workloads need no extra handling.
How do I render a summary/footer row?
Use the footer slot — it receives columnSize, letting you render summary cells per column:
<STable :columns="columns" :data="data" :row-key="row => row.id">
<template #footer="{ columnSize }">
<STableRow>
<STableCell :colspan="columnSize">Total: {{ data.length }} items</STableCell>
</STableRow>
</template>
</STable>How do I localize table copy?
The empty state and all interaction aria-labels (sort/filter/select/expand/resize) follow the ConfigProvider locale (table.* messages, 14 language packs); override per instance via ConfigProvider messages, or use the same-name slots for fully custom content.
Engine escape hatch
STable is a component-driven wrapper, but the TanStack engine is never locked away — four graded escape hatches:
- Slot props — every header/data-cell slot receives
table(the engine table instance) andengineColumn(the engine column instance) alongsidecolumn/row/value:
<STable :columns="columns" :data="data" :row-key="row => row.id">
<template #age="{ value, table }">
{{ value }} ({{ table.getPreFilteredRowModel().flatRows.length }} rows total)
</template>
</STable>useTableEngine()— inject the engine from any component rendered inside the table subtree (deep custom cells, toolbar widgets):
import { useTableEngine } from '@vean/aria/table';
const table = useTableEngine();
table.value.setGrouping([{ id: 'city' }]);- Template ref — the engine instance is the exposed root, so control the table directly from outside:
const tableRef = useTemplateRef<TableEngineTable>('tableRef');
tableRef.value.previousPage();tableOptionsprop — pass raw TanStack table options through for everything the component does not wire explicitly (pagination, row selection models, manual server modes,meta,initialState, faceted values…). Keys the component owns (state wiring, change handlers) take precedence:
<STable
:columns="columns"
:data="data"
:row-key="row => row.id"
:table-options="{
enableRowSelection: true,
getFilteredSelectedRowModel: createFilteredSelectedRowModel()
}"
/>Cell customization priority: named slot > columnDef.cell render function (TanStack-native, rendered via FlexRender) > plain value text. Headers follow the same chain with columnDef.header.
Migration from v0.4x
v0.50.0 replaces the hand-rolled column model with @tanstack/vue-table. TanStack naming is the first citizen and legacy fields get no aliases (dataIndex / sortState and friends must be renamed by hand); the most frequent legacy fields map as follows:
| v0.4x (legacy) | v0.50.0 (TanStack-first) |
|---|---|
{ title, dataIndex } | { header, accessorKey } (dataIndex alias removed) |
sorter: true / sorter(a, b) | enableSorting: true / sortFn: (rowA, rowB) => number |
filter: true / filter: { ... } | enableColumnFilter: true / filterPlaceholder / filterOptions |
width: '140px' / minWidth | size: 140 / minSize (px numbers) |
group column { key, children } | { id, columns } (TanStack group shape) |
sortState / defaultSortState | sorting / defaultSorting (SortingState) — alias removed |
filterState / defaultFilterState | columnFilters / defaultColumnFilters (ColumnFiltersState) |
columnWidths / defaultColumnWidths | columnSizing / defaultColumnSizing (Record<string, number>) |
expanded: R[] | expanded: ExpandedState ({ '1': true } or true) |
update:sortState / update:filterState | update:sorting / update:columnFilters |
Before / after
<!-- v0.4x -->
<script setup>
const columns = [
{ title: 'Name', dataIndex: 'name', sorter: true, width: '180px' },
{ title: 'Age', dataIndex: 'age', align: 'center', filter: true }
];
const expanded = ref([1]);
</script>
<!-- v0.50.0 -->
<script setup>
const columns = [
{ header: 'Name', accessorKey: 'name', enableSorting: true, size: 180 },
{ header: 'Age', accessorKey: 'age', align: 'center', enableColumnFilter: true }
];
const expanded = ref({ 1: true });
</script>