Tabs
A set of layered panels with one visible at a time, built on Base UI. Four list variants and a sliding indicator that animates between triggers.
Playground
<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
<TabsIndicator />
</TabsList>
</Tabs>Preview
Compose TabsList with TabsTriggers and a TabsIndicator, then match each trigger's value with a TabsContent panel.
Account settings content.
<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
<TabsIndicator />
</TabsList>
<TabsContent value="account">Account settings content.</TabsContent>
<TabsContent value="password">Password settings content.</TabsContent>
<TabsContent value="settings">General settings content.</TabsContent>
</Tabs>Installation
npx shadcn@latest add https://espresso-base-ui.vercel.app/r/tabs.jsonUsage
import {
Tabs,
TabsContent,
TabsIndicator,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs"<Tabs defaultValue="one">
<TabsList>
<TabsTrigger value="one">One</TabsTrigger>
<TabsTrigger value="two">Two</TabsTrigger>
<TabsIndicator />
</TabsList>
<TabsContent value="one">First panel.</TabsContent>
<TabsContent value="two">Second panel.</TabsContent>
</Tabs>Variants
TabsList takes a variant: default (raised segment on a secondary surface), line (underline indicator on a bottom border), ghost (raised segment, no track background), and browser (bordered browser-style tabs). The indicator restyles itself per variant.
<Tabs defaultValue="account">
<TabsList variant="line">
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsIndicator />
</TabsList>
</Tabs>
<Tabs defaultValue="account">
<TabsList variant="ghost">
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsIndicator />
</TabsList>
</Tabs>
<Tabs defaultValue="account">
<TabsList variant="browser">
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsIndicator />
</TabsList>
</Tabs>Sizes
size on TabsList is sm by default; default is slightly taller with roomier padding and medium-weight labels.
<Tabs defaultValue="account">
<TabsList size="sm">
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsIndicator />
</TabsList>
</Tabs>
<Tabs defaultValue="account">
<TabsList size="default">
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsIndicator />
</TabsList>
</Tabs>With icons
Drop an icon before the label – svg children are auto-sized to size-4. A trigger can also be icon-only, and disabled dims it and blocks activation.
<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account">
<User /> Account
</TabsTrigger>
<TabsTrigger value="password" disabled>
<Lock /> Password
</TabsTrigger>
<TabsTrigger value="settings">
<Settings /> Settings
</TabsTrigger>
<TabsIndicator />
</TabsList>
</Tabs>Vertical
orientation="vertical" stacks the triggers in a column beside the panel; the line variant moves its border and indicator to the right edge of the list, and browser wraps the active tab so it opens into the panel.
Account settings content.
Account settings content.
<Tabs defaultValue="account" orientation="vertical" className="w-full max-w-md">
<TabsList variant="line">
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
<TabsIndicator />
</TabsList>
<TabsContent value="account">Account settings content.</TabsContent>
<TabsContent value="password">Password settings content.</TabsContent>
<TabsContent value="settings">General settings content.</TabsContent>
</Tabs>
<Tabs defaultValue="account" orientation="vertical" className="w-full max-w-md">
<TabsList variant="browser">
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
<TabsIndicator />
</TabsList>
<TabsContent value="account">Account settings content.</TabsContent>
<TabsContent value="password">Password settings content.</TabsContent>
<TabsContent value="settings">General settings content.</TabsContent>
</Tabs>API reference
Each part wraps its Base UI Tabs counterpart (Root, List, Tab, Indicator, Panel) and accepts that primitive's full prop set plus className. Styling props live on TabsList and are read by the triggers and indicator via group data attributes.
Tabs
| Prop | Type | Default | Description |
|---|---|---|---|
| defaultValue | any | – | Value of the tab selected on first render (uncontrolled). |
| value | any | – | Controlled selected value; pair with onValueChange. |
| onValueChange | (value, eventDetails) => void | – | Called when the selected tab changes. |
| orientation | "horizontal" | "vertical" | "horizontal" | Layout direction – horizontal stacks list above panels; vertical places a column of triggers beside them. |
TabsList
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "default" | "line" | "ghost" | "browser" | "default" | Track style – secondary-surface segment, underline, borderless segment, or bordered browser tabs. Also restyles the indicator. |
| size | "sm" | "default" | "sm" | Trigger height, padding and font weight for the whole list. |
| Component | Description |
|---|---|
| TabsTrigger | The tab button (data-slot="tabs-trigger"). Takes value and disabled; svg children are auto-sized to size-4. Active text color comes from data-active. |
| TabsIndicator | Sliding active-tab highlight (data-slot="tabs-indicator"), positioned with Base UI's --active-tab-* CSS variables. Render it once inside TabsList, after the triggers. |
| TabsContent | The panel (data-slot="tabs-content") shown when its value matches the selected tab. |
Accessibility & styling hooks
Base UI wires up the tabs pattern: the list renders role="tablist", triggers are tabs linked to their tabpanel via aria-controls/aria-selected, and arrow keys move activation (Up/Down in vertical orientation). Every part exposes a data-slot attribute (tabs, tabs-list, tabs-trigger, tabs-indicator, tabs-content); the root reflects data-orientation, the list reflects data-variant and data-size, and the active trigger carries data-active – target these from CSS for app-level overrides.
The tabsListVariants cva helper is also exported for building custom tab strips with the same track classes:
import { tabsListVariants } from "@/components/ui/tabs"
<div className={cn(tabsListVariants({ variant: "line", size: "default" }))}>
...
</div>