Appearance
@vibe-labs/design-vue-tables
Vue 3 data table component for the Vibe Design System. Data-driven architecture with column definitions, slot-based cell customisation, sort composable, and selection composable.
Installation
ts
import { VibeTable, useTableSort, useTableSelection } from "@vibe-labs/design-vue-tables";Requires the CSS layer from @vibe-labs/design-components-tables.
Component
VibeTable
Data-driven table that renders from column definitions and an items array. Provides named slots for every cell and header, plus toolbar/footer attachment points.
Basic Usage
vue
<script setup lang="ts">
import { VibeTable } from "@vibe-labs/design-vue-tables";
import type { TableColumn } from "@vibe-labs/design-vue-tables";
interface User {
id: number;
name: string;
email: string;
role: string;
}
const columns: TableColumn<User>[] = [
{ key: "name", label: "Name" },
{ key: "email", label: "Email", truncate: true },
{ key: "role", label: "Role", width: "120px" },
];
const users: User[] = [
{ id: 1, name: "Alice", email: "alice@example.com", role: "Admin" },
{ id: 2, name: "Bob", email: "bob@example.com", role: "Editor" },
];
</script>
<VibeTable :columns="columns" :items="users" row-key="id" />Custom Cells
vue
<VibeTable :columns="columns" :items="users" row-key="id">
<!-- Custom cell for the 'name' column -->
<template #cell-name="{ item }">
<div class="flex items-center gap-2">
<VibeAvatar :name="item.name" size="xs" />
<span>{{ item.name }}</span>
</div>
</template>
<!-- Custom cell for the 'role' column -->
<template #cell-role="{ item }">
<VibeBadge :label="item.role" variant="accent-subtle" />
</template>
</VibeTable>With Sorting
vue
<script setup lang="ts">
import { VibeTable, useTableSort } from "@vibe-labs/design-vue-tables";
const columns = [
{ key: "name", label: "Name", sortable: true },
{ key: "email", label: "Email", sortable: true },
{ key: "role", label: "Role" },
];
const { sort, sortedItems } = useTableSort({ items: rawUsers });
</script>
<VibeTable :columns="columns" :items="sortedItems" v-model:sort="sort" row-key="id" />With Selection
vue
<script setup lang="ts">
import { VibeTable, useTableSelection } from "@vibe-labs/design-vue-tables";
const { isSelected, toggle, selectedCount, clear } = useTableSelection({
rowKey: "id",
mode: "multi",
});
</script>
<VibeTable :columns="columns" :items="users" :row-selected="isSelected" interactive row-key="id" @row-click="(item) => toggle(item)">
<template #toolbar>
<div v-if="selectedCount > 0">
{{ selectedCount }} selected
<button @click="clear">Clear</button>
</div>
</template>
</VibeTable>With Pagination
vue
<VibeTable :columns="columns" :items="pagedItems" row-key="id">
<template #footer>
<VibePagination v-model:page="page" :total-pages="totalPages" />
</template>
</VibeTable>Variants & Density
vue
<!-- Elevated card-style -->
<VibeTable variant="elevated" :columns="columns" :items="items" />
<!-- Compact density -->
<VibeTable size="sm" :columns="columns" :items="items" />
<!-- Ghost (rows only, no container chrome) -->
<VibeTable variant="ghost" :columns="columns" :items="items" />
<!-- Striped rows -->
<VibeTable striped :columns="columns" :items="items" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
columns | TableColumn<T>[] | required | Column definitions |
items | T[] | required | Row data |
rowKey | TableRowKey<T> | "id" / index | Unique row identifier |
sort | TableSortState | null | undefined | Sort state (v-model:sort) |
variant | TableVariant | "default" | Visual variant |
size | TableSize | "md" | Density — sm / md / lg |
striped | boolean | false | Alternating row backgrounds |
hoverable | boolean | true | Row hover highlight |
interactive | boolean | false | Pointer cursor on rows |
fixedHeader | boolean | false | Sticky header |
full | boolean | true | Width: 100% |
emptyText | string | "No data" | Empty state message |
rowSelected | (item: T) => boolean | — | Selected row predicate |
rowDisabled | (item: T) => boolean | — | Disabled row predicate |
ariaLabel | string | — | Accessible table label |
Events
| Event | Payload | Description |
|---|---|---|
update:sort | TableSortState | null | Sort state changed |
row-click | (item: T, index: number) | Row clicked |
Slots
| Slot | Scope | Description |
|---|---|---|
toolbar | — | Above table (search, filters) |
header-{key} | { column } | Custom header cell content |
cell-{key} | { item, value, column, index } | Custom cell content |
empty | — | Custom empty state |
footer | { itemCount } | Below table (pagination, etc.) |
Composables
useTableSort
Client-side sorting with three-state toggle (asc → desc → clear).
ts
const { sort, sortedItems, toggleSort, clearSort } = useTableSort({
items: rawData, // Ref, getter, or static array
initial: null, // Optional starting sort
comparator: undefined, // Optional custom compare fn
});Returns:
| Property | Type | Description |
|---|---|---|
sort | Ref<TableSortState | null> | Current sort state — bind to v-model:sort |
sortedItems | ComputedRef<T[]> | Sorted items — pass to :items |
toggleSort | (key: string) => void | Cycle sort on a column |
clearSort | () => void | Remove sort, return to natural order |
Default comparator handles numbers, strings (locale-aware with numeric collation), and nulls (sort last).
useTableSelection
Row selection state management — single or multi mode.
ts
const { isSelected, toggle, select, deselect, selectAll, clear, selectedCount } = useTableSelection({
rowKey: "id", // Property name or (item) => key
mode: "multi", // "single" | "multi"
initial: undefined, // Optional initial Set of keys
});Returns:
| Property | Type | Description |
|---|---|---|
selected | Ref<Set<string | number>> | Set of selected row keys |
isSelected | (item: T) => boolean | Predicate — pass to :rowSelected |
toggle | (item: T) => void | Toggle selection |
select | (item: T) => void | Select a row |
deselect | (item: T) => void | Deselect a row |
selectAll | (items: T[]) => void | Select all (multi mode only) |
clear | () => void | Clear all selections |
selectedCount | ComputedRef<number> | Number of selected rows |
ARIA
The component follows standard table semantics:
- Container →
role="table" - Header →
role="row"withrole="columnheader"cells - Body rows →
role="row"withrole="cell"children - Sortable headers →
aria-sort="ascending|descending|none" - Row indexing →
aria-rowindex(1-based, header = 1) - Table label →
aria-labelprop
Column Definition
ts
interface TableColumn<T> {
key: keyof T & string; // Property key on row data
label: string; // Header display text
width?: string; // Fixed width ('120px', '20%')
minWidth?: string; // Minimum width
align?: "left" | "center" | "right";
sortable?: boolean; // Enable sort on this column
truncate?: boolean; // Ellipsis overflow
mono?: boolean; // Monospace + tabular-nums
hidden?: boolean; // Programmatic visibility toggle
cellClass?: string; // Extra CSS class for cells
headerClass?: string; // Extra CSS class for header
}Dependencies
| Package | Purpose |
|---|---|
@vibe-labs/design-components-tables | CSS tokens + generated styles |
Build
bash
npm run buildBuilt with Vite + vite-plugin-dts. Outputs ES module with TypeScript declarations.