Animate UI's own tabs: a highlight glides between triggers and panels slide, with the height following the active panel.
Installation
Usage
import {
Tabs,
TabsContent,
TabsContents,
TabsHighlight,
TabsHighlightItem,
TabsList,
TabsTrigger,
} from '@animate-ui/react/primitives/tabs';
export function Settings() {
return (
<Tabs defaultValue="account">
<TabsHighlight className="absolute inset-0 rounded-md bg-background">
<TabsList className="flex rounded-lg bg-muted p-1">
<TabsHighlightItem value="account" className="flex-1">
<TabsTrigger value="account">Account</TabsTrigger>
</TabsHighlightItem>
<TabsHighlightItem value="password" className="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>
);
}Unlike the library tabs, these are Animate UI's own: no Radix UI, Base UI or React Aria underneath. TabsHighlight and TabsHighlightItem are optional; without them, style the active trigger with data-state="active". Without defaultValue, 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 |
|---|---|---|
defaultValue? | string | - |
value? | string | - |
onValueChange? | (value: string) => void | - |
orientation? | 'horizontal' | 'vertical' | 'horizontal' |
...props? | React.ComponentProps<'div'> | - |
TabsHighlight
Takes the Highlight props, except controlledItems and value.
| 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? | React.ComponentProps<'div'> | - |
TabsTrigger
| Prop | Type | Default |
|---|---|---|
asChild? | 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 |
|---|---|---|
asChild? | boolean | false |
value | string | - |
...props? | HTMLMotionProps<'div'> | - |
useTabs
Returns activeValue, handleValueChange, orientation, and getTriggerId / getContentId to link your own parts by id.