Skip to content

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.

vue
<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

vue
<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.

vue
<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 ​

PropTypeDefaultDescription
modelValuestring—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 ​

PropTypeDefaultDescription
valuestring— (required)Associates the trigger with the TabsContent of the same value.
disabledbooleanfalseRemoves the trigger from keyboard navigation and prevents activation.

TabsContent ​

PropTypeDefaultDescription
valuestring— (required)The tab this panel belongs to.
paddingbooleantrueAdds pt-4 above the panel so content clears the strip.

Events ​

EventPayloadDescription
update:modelValuestringEmitted by Tabs when the active tab changes.

TabsList, TabsTrigger, and TabsContent declare no custom events.

Slots ​

ComponentSlotDescription
TabsdefaultThe tab strip and panels (TabsList + TabsContent).
TabsListdefaultThe TabsTriggers.
TabsTriggerdefaultTrigger content.
TabsContentdefaultPanel 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" with aria-selected and aria-controls, and role="tabpanel" with aria-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.
  • Tabs does not expose reka's orientation, activationMode, or dir props, 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 TabsContent is discarded when you switch away. Keep long-lived state above Tabs (or in a store). The padding prop is purely presentational.

Dark mode & RTL ​

  • The strip uses bg-surface-muted with text-muted, the active trigger lifts to bg-surface / text-foreground with shadow-sm, the focus ring is ring-primary-500, and a disabled trigger is dimmed with opacity-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 under dir="rtl" with no override. reka-ui reads the inherited direction to reverse the arrow keys; set dir="rtl" on an ancestor (or through a ConfigProvider) for correct direction handling.

Released under the MIT License.