Appearance
@vibe-labs/design-vue-tabs
Vue 3 tab components for the Vibe Design System. Compound component architecture with configurable panel mount strategies, keyboard navigation, and full ARIA tab pattern.
Installation
ts
import { VibeTabs, VibeTabList, VibeTab, VibeTabPanels, VibeTabPanel } from "@vibe-labs/design-vue-tabs";Requires the CSS layer from @vibe-labs/design-components-tabs.
Components
VibeTabs
Root container that provides shared context to all tab children via inject.
Usage
vue
<!-- Basic (uncontrolled) -->
<VibeTabs default-value="overview">
<VibeTabList>
<VibeTab name="overview">Overview</VibeTab>
<VibeTab name="specs">Specifications</VibeTab>
<VibeTab name="reviews">Reviews</VibeTab>
</VibeTabList>
<VibeTabPanels>
<VibeTabPanel name="overview">Overview content</VibeTabPanel>
<VibeTabPanel name="specs">Specs content</VibeTabPanel>
<VibeTabPanel name="reviews">Reviews content</VibeTabPanel>
</VibeTabPanels>
</VibeTabs>
<!-- Controlled -->
<VibeTabs v-model="activeTab">
<VibeTabList>
<VibeTab name="one">Tab 1</VibeTab>
<VibeTab name="two">Tab 2</VibeTab>
</VibeTabList>
<VibeTabPanels>
<VibeTabPanel name="one">Content 1</VibeTabPanel>
<VibeTabPanel name="two">Content 2</VibeTabPanel>
</VibeTabPanels>
</VibeTabs>
<!-- Mount strategies -->
<VibeTabs mount-strategy="lazy">...</VibeTabs>
<!-- Mount on first activation, keep (default) -->
<VibeTabs mount-strategy="eager">...</VibeTabs>
<!-- Mount all immediately -->
<VibeTabs mount-strategy="unmount">...</VibeTabs>
<!-- Unmount when deactivated -->Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | string | — | Active tab name (v-model) |
defaultValue | string | first tab | Default tab when uncontrolled |
mountStrategy | TabMountStrategy | "lazy" | Panel mount/unmount behaviour |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | string | Active tab changed |
change | string | Active tab changed |
VibeTabList
Horizontal tab bar with keyboard navigation and variant styling.
vue
<!-- Underline (default) -->
<VibeTabList>
<VibeTab name="a">Tab A</VibeTab>
<VibeTab name="b">Tab B</VibeTab>
</VibeTabList>
<!-- Variants -->
<VibeTabList variant="pills">...</VibeTabList>
<VibeTabList variant="underline">...</VibeTabList>
<!-- Scrollable (overflow) -->
<VibeTabList scrollable>...</VibeTabList>
<!-- Full width (stretch to fill) -->
<VibeTabList full>...</VibeTabList>Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | TabsVariant | — | Visual style |
scrollable | boolean | false | Horizontal scroll on overflow |
full | boolean | false | Stretch tabs to fill width |
ariaLabel | string | — | Accessible label for the tablist |
VibeTab
Individual tab button. Auto-registers with the parent VibeTabs on mount.
vue
<!-- Basic -->
<VibeTab name="settings">Settings</VibeTab>
<!-- With icon -->
<VibeTab name="settings">
<template #icon><SettingsIcon /></template>
Settings
</VibeTab>
<!-- With badge -->
<VibeTab name="notifications" badge="3">Notifications</VibeTab>
<!-- Badge slot -->
<VibeTab name="notifications">
Notifications
<template #badge><VibeBadgeCount :count="unread" /></template>
</VibeTab>
<!-- Disabled -->
<VibeTab name="admin" disabled>Admin</VibeTab>Props
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | required | Unique identifier (must match a panel) |
disabled | boolean | false | Disabled state |
badge | string | — | Badge text |
Slots
| Slot | Description |
|---|---|
default | Tab label |
icon | Leading icon |
badge | Trailing badge content |
VibeTabPanels
Wrapper for tab panels. Thin container — no logic, just layout.
vue
<VibeTabPanels>
<VibeTabPanel name="one">...</VibeTabPanel>
<VibeTabPanel name="two">...</VibeTabPanel>
</VibeTabPanels>VibeTabPanel
Content panel that mounts/shows based on the active tab and mount strategy.
vue
<VibeTabPanel name="settings">
<h2>Settings</h2>
<p>Panel content here.</p>
</VibeTabPanel>Props
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | required | Must match a VibeTab's name |
Mount Strategies
| Strategy | Behaviour | Use When |
|---|---|---|
lazy (default) | Mount on first activation, stay mounted | Most cases — preserves state, avoids upfront cost |
eager | All panels mounted immediately, toggled with v-show | SEO, or panels need to initialise in background |
unmount | Mount on activation, unmount on deactivation | Fresh state needed each time (e.g. forms that should reset) |
Keyboard Navigation
| Key | Action |
|---|---|
| ArrowRight / ArrowDown | Next tab (skips disabled, wraps) |
| ArrowLeft / ArrowUp | Previous tab (skips disabled, wraps) |
| Home | First enabled tab |
| End | Last enabled tab |
Focus follows selection — selecting a tab via keyboard also moves focus to it.
ARIA
The implementation follows the WAI-ARIA Tabs Pattern:
VibeTabList→role="tablist"VibeTab→role="tab",aria-selected,aria-controls, rovingtabindexVibeTabPanel→role="tabpanel",aria-labelledby- IDs auto-generated with unique prefix per
VibeTabsinstance
Dependencies
| Package | Purpose |
|---|---|
@vibe-labs/design-components-tabs | CSS tokens + generated styles |
Build
bash
npm run buildBuilt with Vite + vite-plugin-dts. Outputs ES module with TypeScript declarations.