Animate UI's own tabs: a highlight glides between triggers and panels slide, with the height following the active panel.
Installation
Usage
<script setup lang="ts">
import {
Tabs,
TabsContent,
TabsContents,
TabsHighlight,
TabsHighlightItem,
TabsList,
TabsTrigger,
} from '@animate-ui/vue/primitives/tabs';
</script>
<template>
<Tabs default-value="account">
<TabsHighlight class="absolute inset-0 rounded-md bg-background">
<TabsList class="flex rounded-lg bg-muted p-1">
<TabsHighlightItem value="account" class="flex-1">
<TabsTrigger value="account">Account</TabsTrigger>
</TabsHighlightItem>
<TabsHighlightItem value="password" class="flex-1">
<TabsTrigger value="password">Password</TabsTrigger>
</TabsHighlightItem>
</TabsList>
</TabsHighlight>
<TabsContents>
<TabsContent value="account">Make changes to your account here.</TabsContent>
<TabsContent value="password">Change your password here.</TabsContent>
</TabsContents>
</Tabs>
</template>Unlike the library tabs, these are Animate UI's own: no reka-ui underneath. TabsHighlight and TabsHighlightItem are optional; without them, style the active trigger with data-state="active". Without default-value, the first trigger is selected.
Animation
- The highlight slides to the selected trigger on a spring (stiffness 300, damping 30): one surface is handed from the previous trigger to the new one, at full opacity (Highlight).
- Panels sit side by side and slide to the selected one on a critically damped spring (stiffness 300, damping 35): they glide into place without bouncing past it. The container's height follows the active panel on the same spring. Mounted hidden (in a closed panel, or a tab waiting its turn), the container shows at its height once it's on screen, without growing in. Inactive panels blur (4px, spring with stiffness 300, damping 30) and are
inert. - Keyboard: arrow keys move to the next or previous enabled trigger (wrapping) and select it, Home and End jump to the ends; only the selected trigger is in the Tab order. Keyboard selection animates exactly like a click.
<AnimateUIProvider :tempo>paces every spring.- Under reduced motion, the highlight, panels and height change at once.
API Reference
Tabs
| Prop | Type | Default |
|---|---|---|
default-value? | string | - |
v-model? | string | - |
@update:model-value? | (value: string) => void | - |
orientation? | 'horizontal' | 'vertical' | 'horizontal' |
...props? | HTMLAttributes | - |
TabsHighlight
Takes the Highlight props, except controlled-items and v-model.
| Prop | Type | Default |
|---|---|---|
transition? | Transition | { type: 'spring', stiffness: 300, damping: 30 } |
TabsHighlightItem
Takes the HighlightItem props.
| Prop | Type | Default |
|---|---|---|
value | string | - |
TabsList
| Prop | Type | Default |
|---|---|---|
...props? | HTMLAttributes | - |
TabsTrigger
| Prop | Type | Default |
|---|---|---|
as-child? | boolean | false |
value | string | - |
...props? | HTMLMotionProps<'button'> | - |
TabsContents
Holds the panels and animates between them.
| Prop | Type | Default |
|---|---|---|
transition? | Transition | { type: 'spring', stiffness: 300, damping: 35, restDelta: 0.01 } |
...props? | HTMLMotionProps<'div'> | - |
TabsContent
| Prop | Type | Default |
|---|---|---|
as-child? | boolean | false |
value | string | - |
...props? | HTMLMotionProps<'div'> | - |
useTabs
Returns an activeValue() getter, handleValueChange, an orientation() getter, and getTriggerId / getContentId to link your own parts by id.