Tabs

Several sets of content shown one at a time in the same place.

On my first day at the new school I met a girl on the roof holding an old camera.

<script setup lang="ts">
  import { Stack, Tabs, TabsContent, TabsList, TabsTrigger, Text } from '@hina-ui/vue'
</script>

<template>
  <Tabs default-value="intro" class="w-full max-w-md">
    <TabsList label="Work details">
      <TabsTrigger value="intro">Synopsis</TabsTrigger>
      <TabsTrigger value="volumes">Volumes</TabsTrigger>
      <TabsTrigger value="staff">Staff</TabsTrigger>
    </TabsList>
    <TabsContent value="intro">
      <Stack gap="xs" class="pt-4">
        <Text tone="muted" size="sm">
          On my first day at the new school I met a girl on the roof holding an old camera.
        </Text>
      </Stack>
    </TabsContent>
    <TabsContent value="volumes">
      <Stack gap="xs" class="pt-4">
        <Text tone="muted" size="sm">Seven volumes so far, with the eighth due this winter.</Text>
      </Stack>
    </TabsContent>
    <TabsContent value="staff">
      <Stack gap="xs" class="pt-4">
        <Text tone="muted" size="sm">Written by Isuna Hasekura, illustrated by Jyuu Ayakura.</Text>
      </Stack>
    </TabsContent>
  </Tabs>
</template>
vue

Usage

import { Tabs, TabsList, TabsTrigger, TabsContent } from '@hina-ui/vue'
ts

Four parts: Tabs holds the current selection, TabsList is the row the tabs sit in, TabsTrigger is a single tab, and TabsContent is the panel that belongs to it. Triggers and panels are paired by value.

Three works in progress.

<script setup lang="ts">
  import { Tabs, TabsContent, TabsList, TabsTrigger, Text } from '@hina-ui/vue'
</script>

<template>
  <Tabs default-value="reading" class="w-full max-w-md">
    <TabsList label="Shelf">
      <TabsTrigger value="reading">Reading</TabsTrigger>
      <TabsTrigger value="planned">Want to read</TabsTrigger>
      <TabsTrigger value="finished">Read</TabsTrigger>
    </TabsList>
    <TabsContent value="reading">
      <Text tone="muted" size="sm" class="block pt-4">Three works in progress.</Text>
    </TabsContent>
    <TabsContent value="planned">
      <Text tone="muted" size="sm" class="block pt-4">Twelve works saved for later.</Text>
    </TabsContent>
    <TabsContent value="finished">
      <Text tone="muted" size="sm" class="block pt-4">Forty-eight works finished.</Text>
    </TabsContent>
  </Tabs>
</template>
vue

Tabs switch between sets of content that stand side by side, in no particular order. When the content needs to be visible at once, use section headings instead; when it is a sequence of steps, use Stepper.

Examples

Shapes

underline is the default: a rounded line marks the selected tab, which suits content grouped at the width of the body text. In soft the selection is a raised block, which suits toolbars and compact switches.

A rounded line marks the selected tab.

A raised block marks the selected tab.

<script setup lang="ts">
  import { Stack, Tabs, TabsContent, TabsList, TabsTrigger, Text } from '@hina-ui/vue'
</script>

<template>
  <Stack gap="lg" class="w-full max-w-md">
    <Tabs default-value="a" variant="underline">
      <TabsList label="Underline shape">
        <TabsTrigger value="a">underline</TabsTrigger>
        <TabsTrigger value="b">The default</TabsTrigger>
      </TabsList>
      <TabsContent value="a">
        <Text tone="muted" size="sm" class="block pt-3">
          A rounded line marks the selected tab.
        </Text>
      </TabsContent>
      <TabsContent value="b">
        <Text tone="muted" size="sm" class="block pt-3">
          Suits content grouped at the width of the body text.
        </Text>
      </TabsContent>
    </Tabs>
    <Tabs default-value="a" variant="soft">
      <TabsList label="Block shape">
        <TabsTrigger value="a">soft</TabsTrigger>
        <TabsTrigger value="b">The block shape</TabsTrigger>
      </TabsList>
      <TabsContent value="a">
        <Text tone="muted" size="sm" class="block pt-3">
          A raised block marks the selected tab.
        </Text>
      </TabsContent>
      <TabsContent value="b">
        <Text tone="muted" size="sm" class="block pt-3">Suits toolbars and compact switches.</Text>
      </TabsContent>
    </Tabs>
  </Stack>
</template>
vue

The line or block travels and resizes as the selection moves, rather than jumping.

Sizes

size changes how dense the row is: sm for toolbars and compact interfaces, md as the default, and lg where the row needs more presence, such as the top of a page.

The type size stays the same across all three. Tabs are interface chrome, so their visual weight comes from height, padding and the selection marker.

sm

md

lg

<script setup lang="ts">
  import { Stack, Tabs, TabsContent, TabsList, TabsTrigger, Text } from '@hina-ui/vue'

  const sizes = ['sm', 'md', 'lg'] as const
</script>

<template>
  <Stack gap="lg" class="w-full max-w-md">
    <Stack v-for="size in sizes" :key="size" gap="xs">
      <Text tone="muted" size="sm">{{ size }}</Text>
      <Tabs default-value="a" :size="size">
        <TabsList :label="`Size ${size}`">
          <TabsTrigger value="a">Synopsis</TabsTrigger>
          <TabsTrigger value="b">Volumes</TabsTrigger>
        </TabsList>
        <TabsContent value="a" />
        <TabsContent value="b" />
      </Tabs>
    </Stack>
  </Stack>
</template>
vue

Vertical

Set orientation to vertical and the tabs stack into a column beside the content, with the arrow keys switching to up and down.

Username, email address and password.

<script setup lang="ts">
  import { Tabs, TabsContent, TabsList, TabsTrigger, Text } from '@hina-ui/vue'
</script>

<template>
  <Tabs default-value="account" orientation="vertical" class="w-full max-w-md">
    <TabsList label="Settings sections">
      <TabsTrigger value="account">Account</TabsTrigger>
      <TabsTrigger value="reading">Reading</TabsTrigger>
      <TabsTrigger value="notice">Notifications</TabsTrigger>
    </TabsList>
    <TabsContent value="account">
      <Text tone="muted" size="sm">Username, email address and password.</Text>
    </TabsContent>
    <TabsContent value="reading">
      <Text tone="muted" size="sm">Type size, line height and page direction.</Text>
    </TabsContent>
    <TabsContent value="notice">
      <Text tone="muted" size="sm">Update alerts and site messages.</Text>
    </TabsContent>
  </Tabs>
</template>
vue

When there are too many tabs

Once the tabs overflow their container the row scrolls sideways without showing a scrollbar: edge shadows indicate the scroll position, the mouse wheel scrolls the row directly, and the keyboard cycles through the tabs with the arrow keys.

Works tagged Sci-fi.

<script setup lang="ts">
  import { Tabs, TabsContent, TabsList, TabsTrigger, Text } from '@hina-ui/vue'

  const genres = [
    'Sci-fi',
    'Mystery',
    'Slice of life',
    'School',
    'Healing',
    'Fantasy',
    'Detective',
    'Romance',
    'Historical',
    'War',
  ]
</script>

<template>
  <Tabs default-value="Sci-fi" class="w-full max-w-sm">
    <TabsList label="Genre">
      <TabsTrigger v-for="genre in genres" :key="genre" :value="genre">{{ genre }}</TabsTrigger>
    </TabsList>
    <TabsContent v-for="genre in genres" :key="genre" :value="genre">
      <Text tone="muted" size="sm" class="block pt-4">Works tagged {{ genre }}.</Text>
    </TabsContent>
  </Tabs>
</template>
vue

Disabled

disabled on a TabsTrigger makes that tab unselectable, and the arrow keys skip over it.

Anyone can see this list.

<script setup lang="ts">
  import { Tabs, TabsContent, TabsList, TabsTrigger, Text } from '@hina-ui/vue'
</script>

<template>
  <Tabs default-value="public" class="w-full max-w-md">
    <TabsList label="Visibility">
      <TabsTrigger value="public">Public</TabsTrigger>
      <TabsTrigger value="friends">Followers only</TabsTrigger>
      <TabsTrigger value="private" disabled>Private (sign in required)</TabsTrigger>
    </TabsList>
    <TabsContent value="public">
      <Text tone="muted" size="sm" class="block pt-4">Anyone can see this list.</Text>
    </TabsContent>
    <TabsContent value="friends">
      <Text tone="muted" size="sm" class="block pt-4">Only people who follow you can see it.</Text>
    </TabsContent>
    <TabsContent value="private">
      <Text tone="muted" size="sm" class="block pt-4">Only you can see it.</Text>
    </TabsContent>
  </Tabs>
</template>
vue

Accessibility

  • Give TabsList a label describing what this set of tabs switches between.
  • Arrow keys move between tabs, Home and End jump to the ends, and the selection follows the focus.
  • The panel is focusable, so keyboard users can move from the tabs straight into the content.

API

Tabs

Prop
Type
Default
Description
modelValue
string
—
The selected tab, takes v-model
defaultValue
string
—
The initially selected tab
variant
'underline' | 'soft'
'underline'
Shape
size
'sm' | 'md' | 'lg'
'md'
Size
orientation
'horizontal' | 'vertical'
'horizontal'
Direction
class
string
—
Classes appended to the root

TabsList

Prop
Type
Default
Description
label
string
—
A name for this set of tabs
class
string
—
Classes appended to the row

TabsTrigger

Prop
Type
Default
Description
value
string
—
Identifier pairing it with a panel; required
disabled
boolean
false
Whether the tab can be selected
class
string
—
Classes appended to the tab

TabsContent

Prop
Type
Default
Description
value
string
—
Identifier pairing it with a tab; required
class
string
—
Classes appended to the panel