DataTable
A data table with sorting, row selection, pagination, loading and empty states, built on an accessible native <table>.
Usage
Sorting, selection and pagination
Selection: none
vue
<script setup>
import { ref } from 'vue'
const columns = [
{ key: 'name', label: 'Customer', sortable: true },
{ key: 'city', label: 'City', sortable: true },
{ key: 'orders', label: 'Orders', sortable: true, align: 'end' },
{ key: 'status', label: 'Status' }
]
const rows = ref([/* { id, name, city, orders, status } */])
const sort = ref(null)
const page = ref(1)
const selected = ref([])
</script>
<template>
<SuDataTable
v-model:sort="sort"
v-model:page="page"
v-model:selected="selected"
:columns="columns"
:rows="rows"
caption="Customers"
selectable
:page-size="5"
>
<template #cell-status="{ value }">
<SuTag :label="value" size="sm" />
</template>
</SuDataTable>
</template>Format, alignment and actions column
format changes the display without changing the sorted value; hideLabel visually hides a header (actions column) that is still read.
js
const columns = [
{ key: 'revenue', label: 'Revenue', sortable: true, align: 'end',
format: (value) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value) },
{ key: 'since', label: 'Customer since', sortable: true,
format: (value) => value.toLocaleDateString('en-US') },
{ key: 'actions', label: 'Actions', hideLabel: true, align: 'end' }
]Server-side sorting and pagination
With manual, rows are shown as received: the table emits update:sort and update:page, the server returns the sorted page and total sets the page count.
vue
<SuDataTable
v-model:sort="sort"
v-model:page="page"
:columns="columns"
:rows="pageRows"
:total="total"
:page-size="20"
:loading="loading"
manual
caption="Orders"
@update:sort="fetchPage"
@update:page="fetchPage"
/>Loading and empty state
vue
<SuDataTable :columns="columns" :rows="rows" :loading="loading" />
<SuDataTable :columns="columns" :rows="[]" empty-text="No customers match the filters." />API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
columns | DataTableColumn[] | [] | Columns |
rows | Record<string, any>[] | [] | Rows |
rowKey | string | (row) => string | number | 'id' | Row identifier |
caption | string | - | Table title, region name (or caption slot) |
captionHidden | boolean | false | Title read but visually hidden |
selectable | boolean | false | Selection checkboxes |
rowLabel | (row) => string | first column | Row name for its checkbox |
pageSize | number | - | Rows per page (pagination) |
manual | boolean | false | Sorting and pagination done by the server |
total | number | - | Total rows in manual mode |
loading | boolean | false | Loading: skeletons and aria-busy |
emptyText | string | 'No data' (locale) | Empty state text (or empty slot) |
striped | boolean | false | Alternating rows |
size | 'sm' | 'md' | 'lg' | 'md' | Density |
stickyHeader | boolean | false | Sticky header (with maxHeight) |
maxHeight | string | - | Maximum height (scrolling) |
locale | string | component locale | Alphabetical sort language |
DataTableColumn
| Property | Type | Description |
|---|---|---|
key | string | Row property |
label | string | Header |
sortable | boolean | Sortable column |
align | 'start' | 'center' | 'end' | Alignment (end: tabular figures) |
width | string | Width |
format | (value, row) => string | Displayed text |
sortFn | (a, b) => number | Custom comparison |
hideLabel | boolean | Visually hidden header |
v-model
| Model | Type | Description |
|---|---|---|
v-model:sort | { key, direction: 'asc' | 'desc' } | null | Current sort |
v-model:page | number | Current page |
v-model:selected | (string | number)[] | Checked row ids |
Slots
| Slot | Props | Description |
|---|---|---|
cell-{key} | { row, value, index } | Cell content |
header-{key} | { column } | Header content |
caption | - | Rich title |
empty | - | Empty state (e.g. SuEmptyState) |
Accessibility
- Native
<table>:<caption>, column headers (scope="col") and row headers (first column,scope="row"). - Sorting: button in the header,
aria-sorton the sorted column, announcement "Sorted by City, ascending". Language-aware alphabetical order (accents, numbers inside text), empty cells always last. - Selection: each checkbox is named after its row ("Select Émile A."); the "Select all" checkbox carries the mixed state and acts on the displayed rows.
- The scroll area is focusable (keyboard scrolling) and named by the title.
- Loading:
aria-busy="true"and a "Loading…" text read inside the table.