A general, accessible data table driven by columns and rows: each column names a key, and each row is an object keyed by those keys. It renders a real <table> (caption, scope headers) that scrolls horizontally on narrow screens rather than crushing its columns. Comparison is a configuration rather than a separate component — mark the label column header, give the featured column highlight, and hand each value column an icon — which reproduces the old comparison table. The #cell scoped slot takes over any cell.
Examples
Data table
Columns name a key; rows are objects keyed by those keys. Align a column right.
| Name | Role | Commits |
|---|---|---|
| Ada Lovelace | Engineer | 128 |
| Grace Hopper | Lead | 342 |
<UiTable
caption="Team"
:columns="[
{ key: 'name', label: 'Name' },
{ key: 'role', label: 'Role' },
{ key: 'commits', label: 'Commits', align: 'right' },
]"
:rows="[
{ key: 'ada', name: 'Ada Lovelace', role: 'Engineer', commits: 128 },
{ key: 'grace', name: 'Grace Hopper', role: 'Lead', commits: 342 },
]"
/>Comparison mode
Mark the label column header, the featured column highlight, and give each value column an icon — the old comparison table as a configuration.
| Feature | Numori | The usual suite |
|---|---|---|
| Open source | The whole ecosystem | The client, sometimes |
| Sync | End-to-end encrypted | Readable on the server |
| AI training | Never | On by default |
<UiTable
caption="How Numori compares"
hide-caption
:columns="[
{ key: 'feature', label: 'Feature', header: true },
{ key: 'numori', label: 'Numori', highlight: true, icon: 'mdi:check-circle' },
{ key: 'usual', label: 'The usual suite', icon: 'mdi:minus-circle-outline' },
]"
:rows="[
{ key: 'oss', feature: 'Open source', numori: 'The whole ecosystem', usual: 'The client, sometimes' },
{ key: 'sync', feature: 'Sync', numori: 'End-to-end encrypted', usual: 'Readable on the server' },
{ key: 'ai', feature: 'AI training', numori: 'Never', usual: 'On by default' },
]"
/>Custom cells
The #cell scoped slot renders any cell — here a badge for a status column.
| Service | Status |
|---|---|
| Sync | active |
| Backup | paused |
<UiTable
caption="Services"
:columns="[
{ key: 'name', label: 'Service' },
{ key: 'status', label: 'Status' },
]"
:rows="[
{ key: 'sync', name: 'Sync', status: 'active' },
{ key: 'backup', name: 'Backup', status: 'paused' },
]"
>
<template #cell="{ column, value }">
<UiBadge v-if="column.key === 'status'" :color="value === 'active' ? 'green' : 'gray'">
{{ value }}
</UiBadge>
<template v-else>{{ value }}</template>
</template>
</UiTable>Props
| Name | Type | Default | Description |
|---|---|---|---|
| columnsrequired | array | — | Column definitions, left to right, each `{ key, label?, align?, header?, highlight?, icon?, width? }`. header renders the column as row headers (`<th scope="row">`); highlight accent-tints it; icon shows a leading MDI icon before each value; align is left/center/right. |
| rowsrequired | array | — | Row objects, each keyed by the column `key`s. |
| caption | string | '' | Table caption, rendered as a real `<caption>`. Visible by default. |
| hideCaption | boolean | false | Keep the caption for assistive technology but hide it visually. |
| rowKey | string | 'key' | Row field used as the v-for key; falls back to the row index. |
Slots
| Slot | Description |
|---|---|
caption | Rich caption content (overrides the caption prop). |
cell | Custom cell content. Scope: `{ row, column, value }`. |