Tabs
Tabs is a thin wrapper around reka-ui's tabs primitive. It ships as four composable pieces — Tabs, TabsList, TabsTrigger, and TabsContent — that you assemble in order. The active tab is controlled through v-model on the root; the children read it from context.
<script setup lang="ts">
import { ref } from 'vue'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@myghf/ui'
const tab = ref('overview')
</script>
<template>
<Tabs v-model="tab">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="vitals">Vitals</TabsTrigger>
</TabsList>
<TabsContent value="overview">Overview panel</TabsContent>
<TabsContent value="vitals">Vitals panel</TabsContent>
</Tabs>
</template>Examples
Basic tabs
TabsList is the role="tablist" strip, each TabsTrigger carries a value, and each TabsContent pairs with a trigger through the same value. A disabled trigger is skipped during keyboard navigation and cannot be activated.
Summary of the patient's current episode of care.
Active tab: overview
<script setup lang="ts">
import { ref } from 'vue'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@myghf/ui'
const tab = ref('overview')
</script>
<template>
<Tabs v-model="tab" class="max-w-md">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="vitals">Vitals</TabsTrigger>
<TabsTrigger value="history" disabled>History</TabsTrigger>
</TabsList>
<TabsContent value="overview">
<p class="text-sm text-muted">Summary of the patient's current episode of care.</p>
</TabsContent>
<TabsContent value="vitals">
<p class="text-sm text-muted">Heart rate, blood pressure, and oxygen saturation.</p>
</TabsContent>
<TabsContent value="history">
<p class="text-sm text-muted">This panel is unreachable while its trigger is disabled.</p>
</TabsContent>
</Tabs>
<p class="mt-3 text-sm text-muted">Active tab: {{ tab }}</p>
</template>Controlled value
The root is fully controlled: keep the active value in your own state and update it from anywhere, including controls outside the tab strip. TabsContent accepts padding (default true) — set it to false to manage spacing yourself.
First panel. The active value lives in your own ref.
<script setup lang="ts">
import { ref } from 'vue'
import { Button, Tabs, TabsContent, TabsList, TabsTrigger } from '@myghf/ui'
const tab = ref('alpha')
</script>
<template>
<div class="space-y-3">
<Tabs v-model="tab" class="max-w-md">
<TabsList>
<TabsTrigger value="alpha">Alpha</TabsTrigger>
<TabsTrigger value="beta">Beta</TabsTrigger>
</TabsList>
<TabsContent value="alpha">
<p class="text-sm text-muted">First panel. The active value lives in your own ref.</p>
</TabsContent>
<TabsContent value="beta">
<p class="text-sm text-muted">Second panel. Set <code>padding</code> to <code>false</code> to control spacing yourself.</p>
</TabsContent>
</Tabs>
<Button variant="outline" size="sm" @click="tab = tab === 'alpha' ? 'beta' : 'alpha'">
Switch externally
</Button>
</div>
</template>Props
Tabs
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | string | — | Value of the active tab. Bind it with v-model. |
Tabs renders reka's TabsRoot and forwards only modelValue; the reka defaults apply (horizontal orientation, automatic activation, inactive panels unmounted).
TabsList
No props. Renders reka's TabsList as the tab strip; extra attributes fall through to the underlying element.
TabsTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — (required) | Associates the trigger with the TabsContent of the same value. |
disabled | boolean | false | Removes the trigger from keyboard navigation and prevents activation. |
TabsContent
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — (required) | The tab this panel belongs to. |
padding | boolean | true | Adds pt-4 above the panel so content clears the strip. |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | string | Emitted by Tabs when the active tab changes. |
TabsList, TabsTrigger, and TabsContent declare no custom events.
Slots
| Component | Slot | Description |
|---|---|---|
Tabs | default | The tab strip and panels (TabsList + TabsContent). |
TabsList | default | The TabsTriggers. |
TabsTrigger | default | Trigger content. |
TabsContent | default | Panel content. |
None of the four components renders named slots.
Exposed methods
None. Tabs, TabsList, TabsTrigger, and TabsContent do not call defineExpose. Drive the component through the bound modelValue.
Accessibility
- reka-ui implements the WAI-ARIA tabs pattern:
role="tablist",role="tab"witharia-selectedandaria-controls, androle="tabpanel"witharia-labelledby. Triggers are real<button>s. - Keyboard: the strip uses a roving tabindex, so Tab reaches the active tab and Arrow Left/Arrow Right (and Home/End) move between triggers. With the default automatic activation, moving focus also activates the tab.
Tabsdoes not expose reka'sorientation,activationMode, ordirprops, so you cannot switch to vertical layout or manual activation through the wrapper.- Inactive panels are unmounted by default, so any local state inside a hidden
TabsContentis discarded when you switch away. Keep long-lived state aboveTabs(or in a store). Thepaddingprop is purely presentational.
Dark mode & RTL
- The strip uses
bg-surface-mutedwithtext-muted, the active trigger lifts tobg-surface/text-foregroundwithshadow-sm, the focus ring isring-primary-500, and a disabled trigger is dimmed withopacity-50. All colours come from semantic tokens, so both themes adapt automatically. - The strip and triggers use only logical flow utilities (
inline-flex,gap,justify-start), so the row mirrors underdir="rtl"with no override. reka-ui reads the inherited direction to reverse the arrow keys; setdir="rtl"on an ancestor (or through a ConfigProvider) for correct direction handling.