Skip to content

@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

PropTypeDefaultDescription
columnsTableColumn<T>[]requiredColumn definitions
itemsT[]requiredRow data
rowKeyTableRowKey<T>"id" / indexUnique row identifier
sortTableSortState | nullundefinedSort state (v-model:sort)
variantTableVariant"default"Visual variant
sizeTableSize"md"Density — sm / md / lg
stripedbooleanfalseAlternating row backgrounds
hoverablebooleantrueRow hover highlight
interactivebooleanfalsePointer cursor on rows
fixedHeaderbooleanfalseSticky header
fullbooleantrueWidth: 100%
emptyTextstring"No data"Empty state message
rowSelected(item: T) => booleanSelected row predicate
rowDisabled(item: T) => booleanDisabled row predicate
ariaLabelstringAccessible table label

Events

EventPayloadDescription
update:sortTableSortState | nullSort state changed
row-click(item: T, index: number)Row clicked

Slots

SlotScopeDescription
toolbarAbove table (search, filters)
header-{key}{ column }Custom header cell content
cell-{key}{ item, value, column, index }Custom cell content
emptyCustom 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:

PropertyTypeDescription
sortRef<TableSortState | null>Current sort state — bind to v-model:sort
sortedItemsComputedRef<T[]>Sorted items — pass to :items
toggleSort(key: string) => voidCycle sort on a column
clearSort() => voidRemove 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:

PropertyTypeDescription
selectedRef<Set<string | number>>Set of selected row keys
isSelected(item: T) => booleanPredicate — pass to :rowSelected
toggle(item: T) => voidToggle selection
select(item: T) => voidSelect a row
deselect(item: T) => voidDeselect a row
selectAll(items: T[]) => voidSelect all (multi mode only)
clear() => voidClear all selections
selectedCountComputedRef<number>Number of selected rows

ARIA

The component follows standard table semantics:

  • Container → role="table"
  • Header → role="row" with role="columnheader" cells
  • Body rows → role="row" with role="cell" children
  • Sortable headers → aria-sort="ascending|descending|none"
  • Row indexing → aria-rowindex (1-based, header = 1)
  • Table label → aria-label prop

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

PackagePurpose
@vibe-labs/design-components-tablesCSS tokens + generated styles

Build

bash
npm run build

Built with Vite + vite-plugin-dts. Outputs ES module with TypeScript declarations.