Vitral 0.2
Data

DataTable

The small table: rows already in hand, sorted, striped and sized, with a sticky head. DataGrid takes over when it needs more.

Import

main.ts
import { DataTable } from '@vitral/vue';

Default

Rows in the order they were given. Nothing else is switched on.

VersionNamePublished
0.4.0Spreadsheet2026-08-14
0.3.2Taskboard2026-06-02
0.3.0Schedule2026-04-21
0.2.1Chart2026-02-09
0.2.0Editor2025-12-18
0.1.0Foundations2025-10-30
<script setup lang="ts">import { DataTable } from '@vitral/vue'; const releases = [    { version: '0.4.0', name: 'Spreadsheet', published: '2026-08-14', downloads: 12840 },    { version: '0.3.2', name: 'Taskboard', published: '2026-06-02', downloads: 9310 },    { version: '0.3.0', name: 'Schedule', published: '2026-04-21', downloads: 24105 },    { version: '0.2.1', name: 'Chart', published: '2026-02-09', downloads: 31760 },    { version: '0.2.0', name: 'Editor', published: '2025-12-18', downloads: 8422 },    { version: '0.1.0', name: 'Foundations', published: '2025-10-30', downloads: 4190 }];</script> <template>    <DataTable :value="releases" data-key="version" aria-label="Releases">        <DataTable.Column field="version" header="Version" />        <DataTable.Column field="name" header="Name" />        <DataTable.Column field="published" header="Published" />    </DataTable></template>

Sortable

Click a header to sort by it: ascending, then descending, then back to the order the rows arrived in. One column at a time — a sort over several columns is DataGrid's.

0.4.0SpreadsheetAug 14, 202612,840
0.3.2TaskboardJun 2, 20269,310
0.3.0ScheduleApr 21, 202624,105
0.2.1ChartFeb 9, 202631,760
0.2.0EditorDec 18, 20258,422
0.1.0FoundationsOct 30, 20254,190
<script setup lang="ts">import { DataTable } from '@vitral/vue'; interface Release {    version: string;    name: string;    published: string;    downloads: number;} const releases: Release[] = [    { version: '0.4.0', name: 'Spreadsheet', published: '2026-08-14', downloads: 12840 },    { version: '0.3.2', name: 'Taskboard', published: '2026-06-02', downloads: 9310 },    { version: '0.3.0', name: 'Schedule', published: '2026-04-21', downloads: 24105 },    { version: '0.2.1', name: 'Chart', published: '2026-02-09', downloads: 31760 },    { version: '0.2.0', name: 'Editor', published: '2025-12-18', downloads: 8422 },    { version: '0.1.0', name: 'Foundations', published: '2025-10-30', downloads: 4190 }]; const number = new Intl.NumberFormat('en-US');const date = (iso: string) => new Date(`${iso}T00:00:00`).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });</script> <template>    <DataTable :value="releases" data-key="version" striped-rows row-hover aria-label="Releases by column">        <DataTable.Column field="version" header="Version" sortable />        <DataTable.Column field="name" header="Name" sortable />        <DataTable.Column field="published" header="Published" sortable>            <template #body="{ data }">{{ date((data as Release).published) }}</template>        </DataTable.Column>        <DataTable.Column field="downloads" header="Downloads" sortable align="right">            <template #body="{ data }">{{ number.format((data as Release).downloads) }}</template>        </DataTable.Column>    </DataTable></template>

Grid lines, sizes and a header of your own

A line between columns as well as between rows, tighter or roomier cells, and the header and footer as content you write.

Recent releases
VersionNameDownloads
0.4.0Spreadsheet12,840
0.3.2Taskboard9,310
0.3.0Schedule24,105
0.2.1Chart31,760
<script setup lang="ts">import { DataTable } from '@vitral/vue'; interface Release {    version: string;    name: string;    published: string;    downloads: number;} const releases: Release[] = [    { version: '0.4.0', name: 'Spreadsheet', published: '2026-08-14', downloads: 12840 },    { version: '0.3.2', name: 'Taskboard', published: '2026-06-02', downloads: 9310 },    { version: '0.3.0', name: 'Schedule', published: '2026-04-21', downloads: 24105 },    { version: '0.2.1', name: 'Chart', published: '2026-02-09', downloads: 31760 },    { version: '0.2.0', name: 'Editor', published: '2025-12-18', downloads: 8422 },    { version: '0.1.0', name: 'Foundations', published: '2025-10-30', downloads: 4190 }]; const number = new Intl.NumberFormat('en-US');</script> <template>    <DataTable :value="releases.slice(0, 4)" data-key="version" show-gridlines size="small" aria-label="Recent releases">        <template #header><strong>Recent releases</strong></template>        <DataTable.Column field="version" header="Version" />        <DataTable.Column field="name" header="Name" />        <DataTable.Column field="downloads" header="Downloads" align="right">            <template #body="{ data }">{{ number.format((data as Release).downloads) }}</template>        </DataTable.Column>        <template #footer>{{ releases.length }} releases in all</template>    </DataTable></template>

Its own scroller

Given a height, the table scrolls inside it and the head stays put. The stuck head takes an opaque background, or rows would show through it.

Published
0.4.0Spreadsheet2026-08-14
0.3.2Taskboard2026-06-02
0.3.0Schedule2026-04-21
0.2.1Chart2026-02-09
0.2.0Editor2025-12-18
0.1.0Foundations2025-10-30
<script setup lang="ts">import { DataTable } from '@vitral/vue'; const releases = [    { version: '0.4.0', name: 'Spreadsheet', published: '2026-08-14', downloads: 12840 },    { version: '0.3.2', name: 'Taskboard', published: '2026-06-02', downloads: 9310 },    { version: '0.3.0', name: 'Schedule', published: '2026-04-21', downloads: 24105 },    { version: '0.2.1', name: 'Chart', published: '2026-02-09', downloads: 31760 },    { version: '0.2.0', name: 'Editor', published: '2025-12-18', downloads: 8422 },    { version: '0.1.0', name: 'Foundations', published: '2025-10-30', downloads: 4190 }];</script> <template>    <DataTable :value="releases" data-key="version" scroll-height="11rem" striped-rows aria-label="Releases, scrolling">        <DataTable.Column field="version" header="Version" sortable />        <DataTable.Column field="name" header="Name" sortable />        <DataTable.Column field="published" header="Published" />    </DataTable></template>

Nothing to show

With no rows, one cell spans the table and says so. `empty` — the slot or `<DataTable.Empty>` — replaces the words.

VersionName
No releases yet. The first one is on its way.
<script setup lang="ts">import { DataTable } from '@vitral/vue';</script> <template>    <DataTable :value="[]" aria-label="No releases">        <DataTable.Column field="version" header="Version" />        <DataTable.Column field="name" header="Name" />        <DataTable.Empty>No releases yet. The first one is on its way.</DataTable.Empty>    </DataTable></template>

API

Read from packages/vue/src/components/DataTable/types.ts, so it says what the component actually accepts.

Props

NameTypeDescription
valuereadonly unknown[]The rows, already in hand. This table does not fetch, page or filter.
dataKeystringThe field that identifies a row, used as its key. Falls back to its position.
captionstringShown above the head, for a screen reader; use the `header` slot for something visible.
stripedRowsbooleanEvery other row tinted.
showGridlinesbooleanA line between columns as well as between rows.
rowHoverbooleanRows light up under the pointer. Worth it when a row is a link or opens something.
size'small' | 'large'—
scrollHeightstringGives the table its own scroller of this height, and sticks the head to the top of it.
emptyMessagestringWhat to say when there are no rows; the `empty` slot wins over it.

Plus pt, dt and unstyled from BaseProps, see pass-through and unstyled mode.

Slots

NameSlot propsDescription
default() => VNode[]The `<Column>`s. Any other content here is ignored.
header() => VNode[]—
footer() => VNode[]—
empty() => VNode[]—