Skip to content

Table ​

Data table built on TanStack Table v8. Aurora wraps TanStack with sensible aurora-flavored defaults: sorting indicators, pagination, column visibility, row selection, and persistence via the DS_TABLE_SETTINGS adapter.

ID
First Name
Last Name
Age
Status
1
John
Doe
25
Active
2
Jane
Doe
23
Inactive
3
Michael
Smith
32
Active
4
Emily
Johnson
28
Pending
5
David
Williams
35
Active
6
David1
Williams
35
Active
7
David2
Williams
35
Active
8
David3
Williams
35
Active
9
David4
Williams
35
Active
10
David5
Williams
35
Active
11
David6
Williams
35
Active
1–11 general.of 11

The column schema uses TanStack's createColumnHelper<T>() — pass your row type for cell-typed safety.

When to reach for a table ​

  • Structured datasets with multiple comparable attributes per row — the user wants to scan, sort, or compare across columns
  • ESG data — buildings, meters, certifications, surveys, audits
  • Admin views and audit logs — long lists where filtering and bulk action matter
  • Sortable, filterable, or bulk-actionable content — selection + the floating action bar live here

When not to use a table ​

  • Small datasets (handful of items, few attributes) — cards or a plain list read better
  • Single-column content — that's a list, not a table
  • Deeply nested hierarchies — use a tree view
  • A form layout — fields belong in Input / Combobox / etc., laid out as a form. Tables are for displaying rows of data.

Basic example (everything enabled) ​

vue
<script setup lang="ts">
import { ref } from 'vue'
import { Table } from '@scaler-tech/aurora/table'
import { createColumnHelper } from '@tanstack/vue-table'

type Person = { id: number; firstName: string; lastName: string; age: number; status: string }

const columnHelper = createColumnHelper<Person>()

const columns = [
    columnHelper.accessor('id', { header: 'ID', cell: info => info.getValue() }),
    columnHelper.accessor('firstName', { header: 'First Name', cell: info => info.getValue() }),
    columnHelper.accessor('lastName', { header: 'Last Name', cell: info => info.getValue() }),
    columnHelper.accessor('age', { header: 'Age', cell: info => info.getValue() }),
    columnHelper.accessor('status', { header: 'Status', cell: info => info.getValue() }),
]

const data = ref<Person[]>([
    { id: 1, firstName: 'John', lastName: 'Doe', age: 25, status: 'Active' },
    /* … */
])
</script>

<Table
  :data="data"
  :columns="columns"
  row-id-key="id"
  :enable-sorting
  :enable-column-resizing
  :enable-pagination
  :enable-column-visibility
  :enable-row-selection
/>

Minimal — no extra features ​

Pagination, sorting, and the rest are off by default-ish; pass exactly what you want.

ID
First Name
Last Name
Age
Status
1
Aurora
Borealis
0
Active
2
Inter
Manrope
0
Active
html
<Table :data="rows" :columns="columns" row-id-key="id" :enable-pagination="false" />

Loading ​

1–3 general.of 3
html
<Table :data="data" :columns="columns" row-id-key="id" :loading="true" />

Props (subset — most-used) ​

PropTypeDefaultDescription
dataT[]requiredRow data array.
columnsColumnDef<T>[]requiredColumn definitions from createColumnHelper<T>().
rowIdKeykeyof T (string/number-valued)requiredField that uniquely identifies a row — feeds TanStack's getRowId, so selection, expansion, and reorder state survive data refreshes. Falls back to the array index for rows missing the value.
enableSortingbooleantrueShow sort indicators in headers; clicking toggles sort.
enableColumnResizingbooleantrueDrag the column edge to resize.
enablePaginationbooleantrueRender pagination footer.
enableColumnVisibilitybooleanfalseShow the column-toggle menu in the header.
enableRowSelectionbooleanfalseRender checkbox column.
enableRowReorderingbooleanfalseDrag rows to reorder.
enableColumnReorderingbooleanfalseDrag columns to reorder.
enableColumnFiltersbooleanfalsePer-column filter UI.
enableGlobalFilterbooleanfalseGlobal search input.
loadingbooleanfalseShow loading skeletons.
pagination{ pageIndex?: number; pageSize?: number; pageSizeOptions?: number[]; serverSide?: boolean }{ pageSize: 25, pageSizeOptions: [10, 25, 50, 100] }Pagination configuration.
columnPinning{ left?: string[]; right?: string[] }{ left: [], right: [] }Pin columns to the start/end.
pinnedTopRowIdsstring[][]Ids of rows to render above the others (TanStack row pinning). See "Pinned rows".
pinnedRowClassstring—Classes applied to every cell of a pinned row, replacing its white background.
defaultColumnSizing{ minSize?: number; maxSize?: number; size?: number }{ size: 0, minSize: 0 }Default sizing applied to all columns.
showInternalBordersbooleantrueRender borders between cells.
hideInternalVerticalBordersbooleanfalseHide vertical cell borders only (rows-only dividers).
hideInternalHorizontalBordersbooleanfalseHide horizontal cell borders only (columns-only dividers).
roundedCornersboolean—Round the outer corners.
stickyScrollbarboolean—Pin the horizontal scrollbar to the viewport bottom.
stickyHeaderbooleanfalsePin the header to the top of the scroll region without fill mode. Only takes visual effect where the table's own scroll box is the vertical scroller.
groupBystring | ((row: T) => string)—Group rows under full-width collapsible header rows. See "Grouped rows".

The data and columns props are TanStack-shaped — refer to TanStack Table docs for advanced column features (filter functions, sort functions, sub-rows, expanding, custom cell renderers).

Custom cell rendering ​

Use a slot named after the column id to override that column's cell rendering — no need to provide a cell function in the column definition.

html
<Table :data="data" :columns="columns" row-id-key="id">
  <template #status="{ value }">
    <Tag :variant="value === 'Active' ? 'success' : 'neutral'">{{ value }}</Tag>
  </template>
</Table>

The slot props give you { cell, row, original, value, index } — value is typed as the column's cell value when you use createColumnHelper<T>().

Pinned rows ​

pinnedTopRowIds lifts named rows above the rest — a totals or summary band that stays put while the reader pages through the records. The pinned rows must be in data (TanStack can only pin a row it knows about) and their id must be a string, so it can never collide with a numeric record id.

html
<Table
  :data="[summaryRow, ...assets]"
  :columns="columns"
  row-id-key="id"
  :pinned-top-row-ids="['summary']"
  pinned-row-class="bg-secondary"
/>
  • Pinned rows show above every page, in the order given, and are excluded from the pagination footer's count — they read as a summary, not as one more record.
  • Cell slots receive the pinned row like any other, so branch on your own flag (original.summary) to render it differently.
  • pinnedRowClass tints the band. It replaces the cell's own background rather than layering over it, so pass one background utility.

Grouped rows ​

Pass groupBy to section the table: each group gets a full-width, collapsible header row above its rows (built on TanStack's grouping + expanded row models). Pass a column id to group by that column's value, or an accessor function when the grouping isn't a column of its own — the returned string doubles as the header label.

html
<Table :data="assets" :columns="columns" row-id-key="id" :group-by="row => row.statusBand" />
  • Groups keep the data's order of first appearance — sort your rows before grouping to control group order.
  • Groups start expanded; clicking a header collapses it.
  • The default header renders the label and the group's row count. Override it with the group-header slot ({ row, label, count, isExpanded, colspan }); aggregates are available via TanStack — put aggregationFn on a column def and read row.getValue(columnId) on the group row.
html
<Table :data="assets" :columns="columns" row-id-key="id" group-by="assetType">
  <template #group-header="{ label, count, row }">
    <span class="paragraph-xs-medium text-primary">{{ label }}</span>
    <span class="paragraph-xs text-secondary">{{ count }} assets · avg {{ row.getValue('intensity') }}</span>
  </template>
</Table>

Grouping is client-side. With server-side pagination it only groups the loaded page, and header rows count toward the page size — it pairs best with enablePagination: false.

Persistence (DS_TABLE_SETTINGS) ​

Column order, row order, column visibility, and saved views all persist through the DS_TABLE_SETTINGS adapter. Aurora ships a no-op default — Table renders without persistence out of the box. Provide a real adapter at the app root via provideTableSettings(app) to enable saving.