Tabs
A set of layered sections — line or pill variants, optional icons and count badges, horizontal or vertical orientation, full keyboard navigation.
- Status
- Since
0.1.0- Accessibility pattern
- APG tabs
Last updated
Install
Add Tabs from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/tabsUsage
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="activity" count={3}>
Activity
</TabsTrigger>
</TabsList>
<TabsContent value="overview">Overview content</TabsContent>
<TabsContent value="activity">Activity content</TabsContent>
</Tabs>;Anatomy
Tabs is a compound component built on Base UI Tabs. Compose the parts inside the root, matching
each TabsTrigger to a TabsContent by value:
<Tabs defaultValue="overview" orientation="horizontal">
<TabsList variant="line">
<TabsTrigger value="overview">
<Icon />
Overview
</TabsTrigger>
<TabsTrigger value="activity" count={3}>
Activity
</TabsTrigger>
</TabsList>
<TabsContent value="overview">…</TabsContent>
<TabsContent value="activity">…</TabsContent>
</Tabs>Tabs— the root that groups the list and panels and ownsorientation(data-slot="tabs",data-orientation). Controlled viavalue/onValueChange, or uncontrolled viadefaultValue.TabsList— the row (or column) of triggers (data-slot="tabs-list",data-variant). Hosts the moving underline indicator for thelinevariant.TabsTrigger— an individual tab button (data-slot="tabs-trigger"). Active state is exposed asdata-active. Compose a leading icon as the first child; passcountfor a trailing badge.TabsContent— the panel shown for the active tab (data-slot="tabs-content"), matched byvalue.
Examples
Variants
line (a moving underline tracks the active tab, the default) and pill (a bounded muted track
where the active tab raises on the shared selected-chip recipe). Both support a leading icon
composed as the first child and a trailing count badge.
On line, the trigger's hover wash is deliberately held one 4px step off the rule the underline
rides along — a wash that runs flush into a container hairline reads as a rendering bug rather than
a state. The gap is a logical margin, so the vertical variant mirrors it onto the inline-start rail
and RTL follows without a second rule.
Orientation
Set orientation="vertical" on the root to stack the tab list beside the panels — useful for
settings-style navigation. Keyboard navigation switches to the up/down arrow keys automatically.
With the line variant, the moving underline indicator rides the inline-start rail instead of the
bottom rule, so it mirrors in RTL:
The pill variant stacks the raised-chip triggers in a column:
Disabled
Mark a TabsTrigger disabled to render it dimmed and non-interactive. It carries data-disabled,
is skipped by keyboard navigation, and cannot be selected:
Chip variant
variant="chip" renders free-standing tabs on the 28px chrome scale — no track, no
underline; the active tab raises on the same selected-chip recipe pill uses — the
pressed/selected rung with the one hairline, and it keeps its own hover and pressed steps. The dense
record-page treatment; pair with count for per-tab totals. For tab sets that outgrow their
container, compose an overflow menu ("+N more") from DropdownMenu — the list itself scrolls
with an edge fade by default.
Playground
Switch between the variants and orientations, then copy the generated JSX.
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="activity">Activity</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>
<TabsContent value="overview">Overview content</TabsContent>
<TabsContent value="activity">Activity content</TabsContent>
<TabsContent value="settings">Settings content</TabsContent>
</Tabs>API Reference
Tabs
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | "horizontal" | "vertical" | 'horizontal' | Layout flow direction. horizontal lays the tab row above the panels;
vertical stacks the tab list beside the panels. |
Data attributes and CSS variables on Tabs
| Attribute | Values |
|---|---|
data-slot | "tabs" |
TabsList
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "chip" | "line" | "pill" | 'line' | Active-tab treatment.
- line: transparent track with a moving underline indicator (default).
- pill: muted track; the active tab raises on the shared selected-chip
recipe (selectedChipVariants) it holds in common with Segmented.
- chip: the same raised chip free-standing, with no track (the dense
record-page treatment). |
Data attributes and CSS variables on TabsList
| Attribute | Values |
|---|---|
data-slot | "tabs-indicator" | "tabs-list" |
data-variant | mirrors a prop or state value |
TabsTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
count | number | — | Optional count rendered as a trailing badge — e.g. unread or item totals. Painted as body ink on a quiet ink wash, so it reads on every variant and state. |
Data attributes and CSS variables on TabsTrigger
| Attribute | Values |
|---|---|
data-slot | "tabs-trigger" | "tabs-trigger-count" |
TabsContent
TabsContent adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
Data attributes and CSS variables on TabsContent
| Attribute | Values |
|---|---|
data-slot | "tabs-content" |
Accessibility
- Built on Base UI Tabs: the list renders with
role="tablist", each trigger withrole="tab", and each panel withrole="tabpanel", all wired together witharia-controls/aria-selected. - Roving tabindex — only the active tab is in the tab order; arrow keys move between tabs within the list.
- Activation is manual by default: arrow keys move focus, Enter/Space selects.
- Every interactive part shows a visible
:focus-visiblering (outline-ring) — neveroutline: none. - Disabled triggers carry
data-disabled, are skipped by keyboard navigation, and are not selectable.
| Key | Action |
|---|---|
| Tab | Move focus into the tab list (lands on the active tab), then out to the panel. |
| ← / → | Move focus between tabs (horizontal orientation). |
| ↑ / ↓ | Move focus between tabs (vertical orientation). |
| Home / End | Move focus to the first / last tab. |
| Enter / Space | Activate the focused tab. |
| Contract | States tested |
|---|---|
| Behaviour | default, active, disabled, selected |
| Accessibility | disabled, focus-visible |
| Visual | default, hover, focus, active, disabled |