Skip to content

Tabs

Switch between sections of the same screen without showing them all at once.

Anna Petrova

Second factor is off

  • Email

    anna@northwind.example

  • Language

    English

<script setup lang="ts">import { ref } from 'vue'import { UiTabs, UiListItem, UiAvatar, UiList, UiSwitch } from 'grani' const pane = ref('overview') const twoFactor = ref(false) const SESSIONS = [  { title: 'MacBook Pro, Lisbon', when: 'This device' },  { title: 'iPhone 15, Lisbon', when: '2 hours ago' },  { title: 'Firefox on Windows, Porto', when: '3 days ago' },]</script> <template>  <UiTabs    v-model="pane"    class="w-full max-w-md"    ariaLabel="Account"    :tabs="[      { value: 'overview', label: 'Overview' },      {        value: 'security',        label: 'Security',        badge: '!',        badgeTone: 'danger',        badgeLabel: 'needs attention',      },      { value: 'sessions', label: 'Sessions', badge: 3, badgeLabel: '3 active' },    ]"  >    <template #header>      <UiListItem title="Anna Petrova" description="Second factor is off">        <template #media><UiAvatar name="Anna Petrova" size="md" /></template>      </UiListItem>    </template>    <template #overview>      <UiList>        <UiListItem title="Email" description="anna@northwind.example" />        <UiListItem title="Language" description="English" />      </UiList>    </template>    <template #security>      <UiList>        <UiListItem          as="label"          title="Second factor"          description="A code from an app at every sign-in."        >          <template #actions><UiSwitch v-model="twoFactor" /></template>        </UiListItem>      </UiList>    </template>    <template #sessions>      <UiList>        <UiListItem v-for="d in SESSIONS" :key="d.title" :title="d.title" :description="d.when" />      </UiList>    </template>  </UiTabs></template>

Also called tab bar, tab strip, tabbed navigation.

Usage

<UiTabs  v-model="value"  :tabs="tabs"  :aria-label="ariaLabel"/>

With GraniResolver in the Vite config, a template needs no import: set it up once.

API

Name · typeDefaultDescription
modelValue*TabValueValue of the active tab (v-model).
tabs*TabItem[]Tabs in order: value, label, disabled, and a badge with its badgeTone and badgeLabel (what it means, in words).
ariaLabel*stringAccessible name of the tab set.
variantpanel | underline= "panel""panel"panel (the default) is the header of a block inside the card shell; underline is a strip on its own.
sizexs | sm | mdStep of a tab on the control ladder: sm 36px, md 44px. Unset, the app's size (createGrani({ size })); xs is read as sm.

Anything else lands on the root element: native attributes, aria-*, data-testid.

When to use another component

When you needTake
A choice with no panesRadio group segmented
Pages of one setPagination
The places of the appApp shell
A block shown or hidden in placeReveal