Tabs

Animate UI's own tabs: a highlight glides between triggers and panels slide, with the height following the active panel.

Overview
View your key metrics and recent project activity. Track progress across all your active projects.
You have 12 active projects and 3 pending tasks.
Analytics
Track performance and user engagement metrics. Monitor trends and identify growth opportunities.
Page views+25%
Visitors3,240
Bounce rate41%
Avg. session2m 14s
Reports
5 reports are ready to export.
Settings
Manage your account preferences and options. Customize your experience to fit your needs.
Configure notifications, security, and themes.
"use client"

import {
  Tabs,
  TabsContent,
  TabsContents,
  TabsHighlight,
  TabsHighlightItem,
  TabsList,
  TabsTrigger,
} from "@animate-ui/react/primitives/tabs"

import {
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

const TABS = [
  { value: "overview", label: "Overview" },
  { value: "analytics", label: "Analytics" },
  { value: "reports", label: "Reports" },
  { value: "settings", label: "Settings" },
]

export default function TabsDemo() {
  return (
    <Tabs defaultValue="overview" className="w-[400px]">
      <TabsHighlight className="absolute inset-0 z-0 rounded-md border border-transparent bg-background shadow-sm dark:border-input dark:bg-input/30">
        <TabsList className="inline-flex h-8 w-fit items-center rounded-lg bg-muted p-[3px] text-muted-foreground">
          {TABS.map((tab) => (
            <TabsHighlightItem key={tab.value} value={tab.value}>
              <TabsTrigger
                value={tab.value}
                className="inline-flex h-full items-center justify-center rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-colors duration-500 ease-in-out hover:text-foreground data-[state=active]:text-foreground dark:text-muted-foreground dark:data-[state=active]:text-foreground"
              >
                {tab.label}
              </TabsTrigger>
            </TabsHighlightItem>
          ))}
        </TabsList>
      </TabsHighlight>
      <TabsContents className="mt-2 rounded-xl bg-card text-sm text-card-foreground ring-1 ring-foreground/10">
        <TabsContent
          value="overview"
          className="flex flex-col gap-(--card-spacing) py-(--card-spacing) [--card-spacing:--spacing(4)]"
        >
            <CardHeader>
              <CardTitle>Overview</CardTitle>
              <CardDescription>
                View your key metrics and recent project activity. Track
                progress across all your active projects.
              </CardDescription>
            </CardHeader>
            <CardContent className="text-sm text-muted-foreground">
              You have 12 active projects and 3 pending tasks.
            </CardContent>
        </TabsContent>
        <TabsContent
          value="analytics"
          className="flex flex-col gap-(--card-spacing) py-(--card-spacing) [--card-spacing:--spacing(4)]"
        >
            <CardHeader>
              <CardTitle>Analytics</CardTitle>
              <CardDescription>
                Track performance and user engagement metrics. Monitor trends
                and identify growth opportunities.
              </CardDescription>
            </CardHeader>
            <CardContent className="grid gap-2 text-sm">
              {[
                ["Page views", "+25%"],
                ["Visitors", "3,240"],
                ["Bounce rate", "41%"],
                ["Avg. session", "2m 14s"],
              ].map(([label, value]) => (
                <div key={label} className="flex items-center justify-between">
                  <span className="text-muted-foreground">{label}</span>
                  <span className="font-medium tabular-nums">{value}</span>
                </div>
              ))}
            </CardContent>
        </TabsContent>
        <TabsContent
          value="reports"
          className="flex flex-col gap-(--card-spacing) py-(--card-spacing) [--card-spacing:--spacing(4)]"
        >
            <CardHeader>
              <CardTitle>Reports</CardTitle>
              <CardDescription>5 reports are ready to export.</CardDescription>
            </CardHeader>
        </TabsContent>
        <TabsContent
          value="settings"
          className="flex flex-col gap-(--card-spacing) py-(--card-spacing) [--card-spacing:--spacing(4)]"
        >
            <CardHeader>
              <CardTitle>Settings</CardTitle>
              <CardDescription>
                Manage your account preferences and options. Customize your
                experience to fit your needs.
              </CardDescription>
            </CardHeader>
            <CardContent className="text-sm text-muted-foreground">
              Configure notifications, security, and themes.
            </CardContent>
        </TabsContent>
      </TabsContents>
    </Tabs>
  )
}

Installation

npm install @animate-ui/react motion
pnpm add @animate-ui/react motion
yarn add @animate-ui/react motion
bun add @animate-ui/react motion

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

PropTypeDefault
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.

PropTypeDefault
transition?
Transition{ type: 'spring', stiffness: 300, damping: 30 }

TabsHighlightItem

Takes the HighlightItem props.

PropTypeDefault
value
string-

TabsList

PropTypeDefault
...props?
React.ComponentProps<'div'>-

TabsTrigger

PropTypeDefault
asChild?
booleanfalse
value
string-
...props?
HTMLMotionProps<'button'>-

TabsContents

Holds the panels and animates between them.

PropTypeDefault
transition?
Transition{ type: 'spring', stiffness: 300, damping: 35, restDelta: 0.01 }
...props?
HTMLMotionProps<'div'>-

TabsContent

PropTypeDefault
asChild?
booleanfalse
value
string-
...props?
HTMLMotionProps<'div'>-

useTabs

Returns activeValue, handleValueChange, orientation, and getTriggerId / getContentId to link your own parts by id.

Built by Skyleen. The source code is available on GitHub.

Last updated: 10/18/2026

On this page