Tabs
Switch between sections of related content within a single view. Built on base-ui Tabs — keyboard navigation, roving focus, and ARIA wiring come for free.
Compose four parts: Tabs (root, owns the active value), TabsList (the trigger strip), TabsTrigger (one per section), and TabsContent (the panel for a value).
Default
The default variant is a filled segmented control — triggers ride on a muted track and the active one lifts onto a solid background.
Line (underline)
variant="line" drops the track and marks the active tab with a 2px underline. This is the right pick for page- or section-level navigation where the tabs sit above their content — e.g. the Users / Permissions split on a role detail page.
Pill
variant="pill" is a borderless strip — transparent rail, muted background on the active trigger only. Good for filter bars and in-card switches where a full segmented control would feel heavy.
With icons
Each TabsTrigger lays out its children in a row with a gap, and auto-sizes any leading svg to size-4. Drop a lucide icon in front of the label.
Sizes
size controls trigger height and text size: md (default) or sm for dense toolbars. Set it once on TabsList — triggers inherit it.
size="sm" — compact, for dense toolbars.size="md" — the default.Vertical orientation
Set orientation="vertical" on the root to stack the list beside its content — the underline / active rail moves to the trigger’s trailing edge. Paired with variant="pill", this is the in-dialog settings-nav pattern (Profile / Billing / Notifications).
Controlled
Tabs is uncontrolled by default — give it defaultValue. To drive it yourself (e.g. to sync the active tab to the URL), pass value and onValueChange.
const [tab, setTab] = useState<"users" | "permissions">("users");
<Tabs value={tab} onValueChange={(v) => setTab(v as typeof tab)}>
<TabsList variant="line">
<TabsTrigger value="users">Users</TabsTrigger>
<TabsTrigger value="permissions">Permissions</TabsTrigger>
</TabsList>
{/* … */}
</Tabs>;Reference
Tabs
Root container. Extends base-ui Tabs.Root props.
| Prop | Type | Default | Description |
|---|---|---|---|
defaultValue | any | — | Initially active tab value (uncontrolled) |
value | any | — | Active tab value (controlled) |
onValueChange | (value) => void | — | Fires when the active tab changes |
orientation | "horizontal" | "vertical" | "horizontal" | Layout direction; also moves the active indicator |
className | string | — | Merged onto the root |
TabsList
The trigger strip. Extends base-ui Tabs.List props.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "line" | "pill" | "default" | Filled segmented control, underline, or borderless strip |
size | "md" | "sm" | "md" | Trigger height and text size; inherited by triggers |
TabsTrigger
One tab button. Extends base-ui Tabs.Tab props.
| Prop | Type | Default | Description |
|---|---|---|---|
value | any | — | Identifies which panel this opens |
TabsContent
A panel. Extends base-ui Tabs.Panel props.
| Prop | Type | Default | Description |
|---|---|---|---|
value | any | — | Matches the trigger that reveals it |