Tab Bar
Overview
Analytics
Settings
Real-time energy monitoring across 847 circuits. Last updated 2 minutes ago.
Documentation
Tabs
Source files: tabs.tsx
Tabs
File: tabs.tsx
Primitive: @radix-ui/react-tabs
Organizes content into mutually exclusive panels, with a tab bar for switching between them. Only one panel is rendered at a time.
Sub-components
| Component | Purpose | Key classes |
|---|---|---|
Tabs |
Root container | flex flex-col gap-2 |
TabsList |
Tab bar | bg-muted text-muted-foreground h-9 rounded-lg p-[3px] |
TabsTrigger |
Individual tab button | Active: bg-background shadow-sm text-foreground; dark active: border-input bg-input/30 |
TabsContent |
Panel content | flex-1 outline-none |
Props (Tabs)
| Prop | Type | Default | Notes |
|---|---|---|---|
defaultValue |
string |
– | Initially active tab (uncontrolled) |
value |
string |
– | Active tab (controlled) |
onValueChange |
(value: string) => void |
– | Callback when active tab changes |
orientation |
"horizontal" \| "vertical" |
"horizontal" |
Layout direction of the tab list |
activationMode |
"automatic" \| "manual" |
"automatic" |
Whether tabs activate on focus or on explicit selection |
Props (TabsTrigger)
| Prop | Type | Default | Notes |
|---|---|---|---|
value |
string |
– | Required. Unique identifier matching a TabsContent |
disabled |
boolean |
false |
Disables the trigger |
Props (TabsContent)
| Prop | Type | Default | Notes |
|---|---|---|---|
value |
string |
– | Required. Matches the corresponding TabsTrigger value |
forceMount |
boolean |
– | Force-mounts the content (useful for animations or preserving state) |
Active state styling
The active trigger is visually distinguished by an elevated background with a subtle shadow:
| State | Token classes |
|---|---|
| Inactive | bg-muted text-muted-foreground (inherits from TabsList) |
| Active (light) | bg-background text-foreground shadow-sm |
| Active (dark) | border-input bg-input/30 text-foreground |
| Disabled | pointer-events-none opacity-50 |
| Focus | border-ring ring-ring/50 ring-[3px] |
Token usage
- Inactive tab bar:
bg-muted,text-muted-foreground - Active tab:
bg-background,text-foreground(light);border-input,bg-input/30(dark) - Shadow:
shadow-smon active trigger (light mode only) - Border:
borderonTabsListcontainer - Focus ring:
border-ring,ring-ring/50 - Border radius:
rounded-lg(list), inherits within triggers
Overflow behavior
When tabs exceed the available width:
- The
TabsListdoes not scroll or wrap by default - For many tabs, wrap
TabsListin aScrollAreawith horizontal orientation - Alternatively, use a
DropdownMenuto expose overflow tabs (the “more” pattern) - On narrow viewports, consider collapsing to a
Selectcomponent instead of tabs
URL synchronization pattern
To sync the active tab with the URL (for bookmarkable tabs):
// Read from searchParams
const searchParams = useSearchParams()
const activeTab = searchParams.get("tab") ?? "overview"
// Update URL on change
function handleTabChange(value: string) {
const params = new URLSearchParams(searchParams)
params.set("tab", value)
router.replace(`?${params.toString()}`, { scroll: false })
}
<Tabs value={activeTab} onValueChange={handleTabChange}>
This pattern keeps the tab state in the URL so it survives page refreshes and can be shared via links.
Accessibility
TabsListrenders withrole="tablist"- Each
TabsTriggerhasrole="tab"andaria-selectedreflecting its state - Each
TabsContenthasrole="tabpanel"and is associated with its trigger viaaria-labelledby - Keyboard navigation:
ArrowLeft/ArrowRightmoves focus between tabs (horizontal orientation)ArrowUp/ArrowDownmoves focus between tabs (vertical orientation)Home/Endmoves to first / last tabEnter/Spaceactivates the focused tab (whenactivationMode="manual")- In
automaticmode, focus alone activates the tab
Data attributes
data-slot="tabs",data-slot="tabs-list",data-slot="tabs-trigger",data-slot="tabs-content"data-state="active"/data-state="inactive"on triggers and content
Exports
Tabs– root containerTabsList– tab barTabsTrigger– individual tab buttonTabsContent– panel content