Back to catalog
Component detail entry
Data Display
@vean/ui
STable

Table

Table belongs to the Data Display category. Start with demos and usage, then use the API tables to finish integration and styling decisions.

Use the quick jump panel to reach overview, usage, demos, and API first, then continue through the full page.

Quick jump

Related components

If you are composing interactions in the same family, these components usually pair well together.

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 + VeanUI align/type/fixed/hidden extensions), grouped headers (columns), index/selection/expand type columns; rowKey keeps row identity stable
  • 🔀 Sorting — enableSorting: true (or sortFn comparator) on a column; controlled sorting / v-model:sorting (TanStack SortingState); aria-sort + localized sort-button aria-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 TanStack ColumnFiltersState (v-model:columnFilters)
  • ✅ Selection — multi-select by default (multiple defaults to true, checkboxes + header select-all); multiple={false} switches to single-select (row radio); controlled selected / v-model:selected
  • 🧩 Expandable & tree rows — expand type column or the expanded-row slot renders expanded rows; children/getChildren drive tree rows (engine getSubRows); expanded follows TanStack ExpandedState
  • 📐 Fixed & resizable — fixed: 'start' | 'end' seeds TanStack column pinning with gradient shadow indicators; resizable columns support pointer drag and arrow-key resizing writing TanStack columnSizing (localized aria-label)
  • ⚡ Performance — virtual + height enable virtualization (custom estimateSize/virtualizerOptions), rendering only visible rows
  • 🎛️ Engine escape hatch — slot props carry table/engineColumn engine instances, useTableEngine() injects the engine anywhere inside the table subtree, the template ref exposes table, and the tableOptions prop 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 use useLocaleMessages (21 table.* messages across 14 language packs)

Component family

  • STable (styled) — entry wrapper; tableVariants recipe (29 slots = 16 Aria slots + 11 filter/selection extra slots + 2 internal radio slots); useOmitProps forwarding + useForwardListeners event merging + full slot passthrough; injects default header-selection/selection/header-sort/header-filter/header-resize/tree-toggle/expand/empty slot content
  • TableCompact (Aria) — aggregation state owner over the @tanstack/vue-table engine: useTableCompactState (sorting/columnFilters/expanded/columnPinning/columnSizing via useControllableState, selected/multiple via useSelection), useTableCompactTable (engine instance wiring), useTableCompactData (header groups/leaf columns/row model), useTableCompactResize (pointer + keyboard column widths), useTableCompactVirtual; provideTableCompactContext bridges the 9 primitives
  • TableRoot (Aria) — root element, dir direction, renders data-vean-table-root and the table semantic container
  • TableScroll / TableContent / TableHeader / TableBody / TableFooter / TableRow / TableHead / TableCell (Aria) — 9 base primitives, all zero-style, each rendering data-vean-table-* data attributes
  • TableCompactHead / 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/clear
  • STableRadio (styled internal) — the row radio used in single-select mode (aria-pressed semantics), consuming the radioRoot/radioIndicator recipe slots directly
  • useTable / usePaginatedTable (UI hooks) — client-side sort/filter/pagination composition hooks

Demos

Basic

#NameAgeHeader AddressDetails
1John Doe30123 Main St
2Jane Smith25456 Elm St
3Bob Johnson40789 Oak St
4Alice Brown35321 Pine St
5Charlie Davis28654 MapleDetail for Charlie Davis
6Eve White32987 Cedar St
7Frank Green45246 Birch St
8Grace Lee27135 Spruce St
9Hank Miller38864 Willow St
10Ivy Wilson29753 Aspen St

Empty

#NameAgeAddress

No data

There is no data to display.

#NameAgeAddress

No matching records

Try adjusting filters or create the first record from here.

Grouped

ProfileMetrics
NameCityAgeScore
Ada LovelaceLondon3696
Linus TorvaldsHelsinki2888
Grace HopperNew York4192

Sorting

NameAgeScore
Ada Lovelace3696
Linus Torvalds2888
Grace Hopper4192

Filtering

NameCityScore
Ada LovelaceLondon96
Linus TorvaldsHelsinki88
Grace HopperNew York92

Fixed

Two columns stay pinned on the left and the score stays pinned on the right while the table scrolls horizontally.

Employee IDNameRoleTeamStatusLocationTimezoneEmailPhoneProjectsUtilizationLast UpdatedScore
ENG-001Ada LovelaceArchitectCore PlatformActiveLondonUTC+0[email protected]+44 20 7946 001491%2026-04-1096
ENG-014Linus TorvaldsMaintainerKernel SystemsReviewingHelsinkiUTC+2[email protected]+358 9 1234 014684%2026-04-1288
ENG-021Grace HopperScientistDeveloper ExperienceActiveNew YorkUTC-4[email protected]+1 212 555 0021589%2026-04-1492
ENG-037Margaret HamiltonTech LeadDesign SystemsPlanningBostonUTC-4[email protected]+1 617 555 0037376%2026-04-0994
ENG-052Katherine JohnsonAnalystData IntelligenceActiveHamptonUTC-4[email protected]+1 757 555 0052793%2026-04-1398

Resizable

Drag any header handle to adjust the column width.

Employee IDNameRoleTeamStatusLocationTimezoneEmailPhoneProjectsUtilizationLast UpdatedScore
ENG-001Ada LovelaceArchitectCore PlatformActiveLondonUTC+0[email protected]+44 20 7946 001491%2026-04-1096
ENG-014Linus TorvaldsMaintainerKernel SystemsReviewingHelsinkiUTC+2[email protected]+358 9 1234 014684%2026-04-1288
ENG-021Grace HopperScientistDeveloper ExperienceActiveNew YorkUTC-4[email protected]+1 212 555 0021589%2026-04-1492
ENG-037Margaret HamiltonTech LeadDesign SystemsPlanningBostonUTC-4[email protected]+1 617 555 0037376%2026-04-0994
ENG-052Katherine JohnsonAnalystData IntelligenceActiveHamptonUTC-4[email protected]+1 757 555 0052793%2026-04-1398

Tree

NameAgeRole
Engineering
12Department
Platform Team
6Team
Design Team
4Team
Operations
8Department

Virtualized

#NameAgeAddress
1User 120Block 1, Soybean Street
2User 221Block 2, Soybean Street
3User 322Block 3, Soybean Street
4User 423Block 4, Soybean Street
5User 524Block 5, Soybean Street
6User 625Block 6, Soybean Street
7User 726Block 7, Soybean Street
8User 827Block 8, Soybean Street
9User 928Block 9, Soybean Street
10User 1029Block 10, Soybean Street
11User 1130Block 11, Soybean Street
12User 1231Block 12, Soybean Street
13User 1332Block 13, Soybean Street
14User 1433Block 14, Soybean Street
15User 1534Block 15, Soybean Street
16User 1635Block 16, Soybean Street

Expandable

NameAgeAddress
John Doe30123 Main St
Jane Smith25456 Elm St
Bob Johnson40789 Oak St

Footer

#NameAgeAddress
1John Doe30123 Main St
2Jane Smith25456 Elm St
3Bob Johnson40789 Oak St
Total: 3 items

Bottom

#NameAgeAddress
1John Doe30123 Main St
2Jane Smith25456 Elm St
3Bob Johnson40789 Oak St
This is the bottom slot content

Multiple Selection

NameAgeAddress
John Doe30123 Main St
Jane Smith25456 Elm St
Bob Johnson40789 Oak St
Selected IDs: [ 1, 2 ]

Single Selection

NameAgeAddress
John Doe30123 Main St
Jane Smith25456 Elm St
Bob Johnson40789 Oak St
Selected: 1

Row Events

NameRoleRegion
Ada LovelaceArchitectLondon
Linus TorvaldsMaintainerHelsinki
Grace HopperCompiler LeadNew 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 + data rendering
  • 02 Variant — default / simple visual variants
  • 03 Bordered — bordered toggle
  • 04 Rounded — rounded toggle
  • 05 Striped — striped zebra rows
  • 06 Empty — empty state (default SEmpty and custom empty slot)
  • 07 Grouped — grouped headers (columns)
  • 08 Sorting — enableSorting + sortFn with controlled/uncontrolled sorting
  • 09 Filtering — enableColumnFilter options and the keyword filter popover
  • 10 Fixed — fixed: 'start' | 'end' fixed columns
  • 11 Resizable — resizable drag/keyboard column resize
  • 12 Tree — tree rows (children data + tree-toggle)
  • 13 Virtualized — virtual + height virtualization
  • 14 Expandable — expand column and expanded-row slot
  • 15 Footer — footer summary slot
  • 16 Bottom — bottom slot
  • 17 Multiple Selection — multi-select (checkboxes + select-all)
  • 18 Single Selection — multiple={false} single-select (radio)
  • 19 Row Events — rowClick/rowDblclick/rowContextmenu row events
  • 20 Sizes — 7 sizes (xs–2xl)

API

Aria Layer

29

Table<T, R, M>

39 Props11 Emits15 Slots
Props
PropTypeDefaultDescription
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 NameParametersDescription
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 NameParametersDescription
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>

32 Props11 Emits15 Slots
Props
PropTypeDefaultDescription
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 NameParametersDescription
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 NameParametersDescription
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

3 Props4 Slots
Props
PropTypeDefaultDescription
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 NameParametersDescription
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

3 Props
Props
PropTypeDefaultDescription
row
TableTreeRow<T extends TableBaseData = TableBaseData, R extends TableUnifiedKey = TableUnifiedKey>
—
Row.
index
number
—
Index of the current item.
colspan
number
—
Colspan.

TableCompactHead

1 Props
Props
PropTypeDefaultDescription
header
TableEngineHeader<T extends TableBaseData = TableBaseData>
—
Engine header instance.

TableCompactRow

3 Props
Props
PropTypeDefaultDescription
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>

14 Props
Props
PropTypeDefaultDescription
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

2 Props
Props
PropTypeDefaultDescription
size
ThemeSize
—
Visual size of the component.
checked
boolean
—
Whether the item is checked.

TableRoot

1 Props
Props
PropTypeDefaultDescription
dir
Direction
—
Reading direction of the component.

TableRow

TableScroll

TableSelection<R, M>

3 Props
Props
PropTypeDefaultDescription
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

4 Props
Props
PropTypeDefaultDescription
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.

CapabilityVeanUIAnt DesignElement PlusNaive UIMantine 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 use accessorKey (or accessorFn), header for the label, size/minSize for pixel widths. The legacy dataIndex/title fields have been removed — rename them to accessorKey/header.
  • Sorting, filtering, and resizing are opt-in per column: enableSorting, enableColumnFilter, resizable (all default false).
  • Group columns nest via columns (TanStack shape); tree rows nest via the row children field or getChildren — these are different concepts and different fields.
  • multiple defaults to true (multi-select); setting it to false renders row radios, makes selected a 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 matching update:sorting (or use v-model:sorting) to write back, otherwise state does not update.
  • The controlled expanded state is a TanStack ExpandedState keyed by String(rowKey(row)) ({ '1': true }, or true for all); defaultExpandAll only 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 it virtual is ignored (falls back to normal rendering).
  • Fixed columns inject a background on data-fixed cells; fixed-column shadows are drawn via the data-fixed-last-start/data-fixed-first-end data attributes — keep these attributes when customizing cell/row slots to preserve the visuals.
  • Filter popover copy (summary, keyword, options, clear) and the empty state follow the ConfigProvider locale; the column label falls back through header → 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:

  1. Slot props — every header/data-cell slot receives table (the engine table instance) and engineColumn (the engine column instance) alongside column/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>
  1. 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' }]);
  1. Template ref — the engine instance is the exposed root, so control the table directly from outside:
const tableRef = useTemplateRef<TableEngineTable>('tableRef');

tableRef.value.previousPage();
  1. tableOptions prop — 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' / minWidthsize: 140 / minSize (px numbers)
group column { key, children }{ id, columns } (TanStack group shape)
sortState / defaultSortStatesorting / defaultSorting (SortingState) — alias removed
filterState / defaultFilterStatecolumnFilters / defaultColumnFilters (ColumnFiltersState)
columnWidths / defaultColumnWidthscolumnSizing / defaultColumnSizing (Record<string, number>)
expanded: R[]expanded: ExpandedState ({ '1': true } or true)
update:sortState / update:filterStateupdate: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>

Changelog

Version history is grouped by release so you can quickly see what changed for this component across published versions.

v0.50.1

2026-10-09

Compare release

Style

stop TanStack default column size from forcing overflow

Scope: tableAuthors: soybeanjsView commite3746

v0.50.0

2026-10-02

Compare release

Style

fix empty row hover style

Scope: tableAuthors: soybeanjsView commitfba7c

Refactor

rebuild table engine on @tanstack/vue-table

Scope: tableAuthors: soybeanjsView commit19b7d

Fix

fix empty row hover style

Scope: tableAuthors: soybeanjsView commitfba7c

Refactor

rebuild table engine on @tanstack/vue-table

Scope: tableAuthors: soybeanjsView commit19b7d

v0.40.0

2026-09-14

Compare release

Style

update rounded prop to false in table styles and components

Scope: tableAuthors: soybeanfeView commit6c496

Style

update rounded prop to false in table styles and components

Scope: tableAuthors: soybeanfeView commit6c496

v0.27.0

2026-05-28

Compare release

Feature

add row interaction events with metadata and update related types

Scope: tableAuthors: soybeanfeView commit93440

v0.15.3

2026-04-27

Compare release

Feature

add empty state support and update documentation

Scope: tableAuthors: soybeanfe, CopilotView commit38ca1

Fix

add missing TableScroll component exports

Scope: tableAuthors: soybeanfeView commit5bbd4

Docs

update table api

Scope: tableAuthors: soybeanfeView commita130d

v0.15.2

2026-04-27

Compare release

Feature

add last start and first end fixed column indicators

Scope: tableAuthors: soybeanfe, CopilotView commite390d

Feature

add table scroll component, enhance table style

Scope: tableAuthors: soybeanfe, CopilotView commit9997c

Fix

set default value for multiple prop in STable component

Scope: tableAuthors: soybeanfeView commit1964c

v0.15.0

2026-04-26

Compare release

Feature

Implementation with a new TableCompact, supports grouped headers, sorting/filtering, fixed columns, column resizing, tree rows, and virtualization. closed #97.

Scope: tableAuthors: CopilotKit in https://github.com/soybeanjs/soybean-ui/issues/97View commit72a94

Feature

implement logical fixed columns and text alignment for RTL support

Scope: tableAuthors: soybeanjsView commitc0fd5

Fix

remove redundant bordered prop from table components

Scope: tableAuthors: soybeanjsView commite2387

Fix

improve column sorting logic in getColumns function

Scope: tableAuthors: soybeanfe, CopilotView commit4de50

Optimization

remove unused data table components and types

Scope: tableAuthors: soybeanjsView commite5683

Optimization

enhance table components with improved column width handling and additional properties

Scope: tableAuthors: soybeanjsView commitb31ff

Optimization

simplify bordered prop and update table styles

Scope: tableAuthors: soybeanjsView commit6d8cb

Optimization

remove unused useSlots import and streamline slot definition

Scope: tableAuthors: soybeanjsView commit434ae

Refactor

Refactor `STable` aggregation into headless `Table`

Scope: tableAuthors: CopilotKitView commit53678

Docs

update bordered prop type to boolean

Scope: tableAuthors: soybeanjsView commitc2a23

Style

remove fixed property from column definition

Scope: tableAuthors: soybeanfeView commitdd475

Fix

improve column sorting logic in getColumns function

Scope: tableAuthors: soybeanfe, CopilotView commit4de50

Style

remove fixed property from column definition

Scope: tableAuthors: soybeanfeView commitdd475

Feature

Implementation with a new TableCompact, supports grouped headers, sorting/filtering, fixed columns, column resizing, tree rows, and virtualization. closed #97.

Scope: tableAuthors: CopilotKit in https://github.com/soybeanjs/soybean-ui/issues/97View commit72a94

Feature

implement logical fixed columns and text alignment for RTL support

Scope: tableAuthors: soybeanjsView commitc0fd5

Fix

remove redundant bordered prop from table components

Scope: tableAuthors: soybeanjsView commite2387

Optimization

remove unused data table components and types

Scope: tableAuthors: soybeanjsView commite5683

Optimization

enhance table components with improved column width handling and additional properties

Scope: tableAuthors: soybeanjsView commitb31ff

Optimization

simplify bordered prop and update table styles

Scope: tableAuthors: soybeanjsView commit6d8cb

Optimization

remove unused useSlots import and streamline slot definition

Scope: tableAuthors: soybeanjsView commit434ae

Refactor

Refactor `STable` aggregation into headless `Table`

Scope: tableAuthors: CopilotKitView commit53678

Docs

update bordered prop type to boolean

Scope: tableAuthors: soybeanjsView commitc2a23

v0.13.6

2026-03-22

Compare release

Fix

enhance column handling in getColumns function for improved type support

Scope: tableAuthors: soybeanjsView commit7c72b

v0.13.5

2026-03-22

Compare release

Fix

ensure columns are checked by default and filter checks accordingly

Scope: tableAuthors: soybeanjsView commit945b5

v0.13.4

2026-03-22

Compare release

Fix

reorder exports for consistency and clarity

Scope: tableAuthors: soybeanjsView commit25840

v0.13.1

2026-03-20

Compare release

Feature

add onFetched callback to usePaginatedTable for handling fetched data

Scope: tableAuthors: soybeanjsView commit22928

v0.13.0

2026-03-20

Compare release

Feature

implement useTable and usePaginatedTable hooks with pagination support

Scope: tableAuthors: soybeanjsView commit1ae61

Fix

update fetch method in usePaginatedTable hook

Scope: tableAuthors: soybeanjsView commitc54b9

Feature

implement useTable and usePaginatedTable hooks with pagination support

Scope: tableAuthors: soybeanjsView commit1ae61

v0.10.6

2026-03-06

Compare release

Fix

adjust header z-index and set multiple default to true

Scope: tableAuthors: soybeanjsView commitf56cf

v0.9.1

2026-03-03

Compare release

Docs

add table docs and examples

Scope: tableAuthors: soybeanjsView commit43882

v0.9.0

2026-03-01

Compare release

Feature

add table component with context, variants, and example usage

Scope: tableAuthors: soybeanjsView commit0fdfd

Fix

remove unused TableCaptionProps from types

Scope: tableAuthors: soybeanjsView commit8dde2