Skip to content

Tabs

An items-driven tab list built on Reka UI's Tabs primitive.

Usage

Tab one content
vue
<script setup lang="ts">
const active = ref('one')
</script>

<template>
  <STabs v-model="active" :items="[{ label: 'One', value: 'one' }, { label: 'Two', value: 'two' }]">
    <template #one>
      Tab one content
    </template>
    <template #two>
      Tab two content
    </template>
  </STabs>
</template>

Switching tabs slides a shared indicator between them rather than each tab carrying its own static active state - the indicator measures the active trigger itself, so it stays correct even if a tab's own width changes.

Custom label content

The label slot replaces a tab's plain-text label with anything - scoped with item and index, so a single template can vary per tab:

3 unread messages.
vue-html
<STabs v-model="active" :items="items">
  <template #label="{ item }">
    <span class="flex items-center gap-2">
      {{ item.label }}
      <SBadge v-if="item.value === 'inbox'" label="3" color="primary" size="sm" />
    </span>
  </template>
  ...
</STabs>

Icons

Give an item an icon and it renders before the label automatically - no label slot needed for the common case:

Profile details.
vue-html
<STabs
  v-model="active"
  :items="[
    { label: 'Profile', value: 'profile', icon: 'hugeicons:user' },
    { label: 'Settings', value: 'settings', icon: 'hugeicons:settings-01' },
  ]"
/>

Variants

variant takes underline (default) or pill, a segmented-control look with the sliding indicator as a raised background instead of a bottom bar:

Showing today.
vue-html
<STabs v-model="active" :items="items" variant="pill" />

Scrollable

Set scrollable when a tab list may outgrow its container. Previous and next chevrons appear only while the labels overflow. The list also supports touch, trackpad, and keyboard navigation, and brings the selected tab into view:

Showing Tab 1.
vue-html
<STabs v-model="active" :items="items" scrollable />

The controls use neutral ghost SButtons by default. Use ui.scrollButton and ui.scrollIcon for styling, or the scroll-left-icon and scroll-right-icon slots to replace only the chevrons. For a different control, replace scroll-left or scroll-right and bind the supplied buttonProps so the button keeps its click handler, disabled state, type, and accessible label:

vue-html
<STabs :items="items" scrollable>
  <template #scroll-right="{ buttonProps }">
    <MyIconButton v-bind="buttonProps" />
  </template>
</STabs>

The full button slots also expose scroll, disabled, and ariaLabel for custom controls that cannot use v-bind="buttonProps".

Orientation

Set orientation="vertical" to place the tab list beside its content. Both underline and pill variants work vertically, and Reka changes arrow-key navigation to Up/Down. scrollable applies only to horizontal tabs.

Manage your account details.
vue-html
<STabs v-model="active" :items="items" orientation="vertical" variant="pill" />

Keep inactive panels mounted

Inactive panel content is unmounted by default. Set unmount-on-hide to false to keep its local state, such as an unfinished form, while the panel is hidden:

vue-html
<STabs :items="items" :unmount-on-hide="false">
  <!-- panel slots -->
</STabs>

In markdown

Every Selaras component is already globally registered under its S-prefixed name - a bare ::s-tabs{...} block works with no extra wiring (see Callout's own "In markdown" section for how the pattern works generally). items is an array prop, not a string - bind it with a leading : so @nuxtjs/mdc parses the attribute as an expression instead of literal text. Each tab's own content is a #slot-name line matching that item's value:

text
::s-tabs{:items='[{"label":"One","value":"one"},{"label":"Two","value":"two"}]'}
#one
Content for the first tab.
#two
Content for the second tab.
::

Custom :ui

Global and scoped recipe overrides use ui.tabs.

To see exactly what you'd be overriding - the current default classes for every slot and variant - here's Tabs's own theme file:

Theme source "tabs" not found

Props

PropTypeDefault
items{ label: string; value?: string; disabled?: boolean; icon?: string }[]-
variant'underline' | 'pill''underline'
orientation'horizontal' | 'vertical''horizontal'
scrollablebooleanfalse
unmountOnHidebooleantrue
modelValuestring-
uiPartial<Record<'root' | 'scrollRoot' | 'scrollViewport' | 'scrollButton' | 'scrollIcon' | 'list' | 'trigger' | 'icon' | 'indicator' | 'content', string | object>>-

Events

EventPayloadDescription
update:modelValuestringFires when the active tab changes

Slots

SlotPropsDescription
[item.value]-Content for that tab, one named slot per item
label{ item, index }Replaces a tab's label content
scroll-left-icon, scroll-right-icon{ class }Replaces a default control's chevron
scroll-left, scroll-right{ buttonProps, scroll, disabled, ariaLabel }Replaces an entire overflow control
Edit this page