Import
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@timelycare/helix-ui"
Props
interface TabsProps {
defaultValue?: string
value?: string
onValueChange?: (value: string) => void
orientation?: "horizontal" | "vertical"
dir?: "ltr" | "rtl"
className?: string
children: React.ReactNode
}
interface TabsListProps {
variant?: "default" | "line"
className?: string
children: React.ReactNode
}
interface TabsTriggerProps {
value: string
disabled?: boolean
className?: string
children: React.ReactNode
}
Variants
Default (Pill / Segment)
The default variant renders tabs inside a muted background container. The active tab appears as a raised pill with a white background and subtle shadow.
| Part | Tailwind |
|---|---|
| TabsList | h-9 bg-muted p-[3px] rounded-lg flex items-center gap-0.5 |
| TabsTrigger | flex-1 px-3 py-1 rounded-md text-sm font-medium text-muted-foreground |
| TabsTrigger (active) | bg-background text-foreground shadow-sm |
| TabsTrigger (inactive) | bg-transparent text-muted-foreground |
| TabsContent | mt-2 |
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>
<TabsContent value="overview">Overview content</TabsContent>
<TabsContent value="analytics">Analytics content</TabsContent>
</Tabs>
Line
Use variant="line" on TabsList for an underline indicator instead of a pill background. The list has a bottom border and the active tab shows a colored underline.
| Part | Tailwind |
|---|---|
| TabsList | border-b border-border flex bg-transparent p-0 |
| TabsTrigger | px-4 py-2 text-sm font-medium text-muted-foreground relative |
| TabsTrigger (active) | text-foreground + bottom border indicator (2px primary) |
| TabsTrigger (inactive) | text-muted-foreground |
<Tabs defaultValue="overview">
<TabsList variant="line">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
</TabsList>
<TabsContent value="overview">Overview content</TabsContent>
</Tabs>
Orientation
Horizontal (default)
Tabs are laid out in a horizontal row. This is the default behavior.
Vertical
Use orientation="vertical" on the Tabs root for a vertical layout. The tabs list stacks vertically and the content panel appears beside it.
| Part | Tailwind |
|---|---|
| TabsList (vertical, default) | flex-col items-stretch h-auto bg-muted p-[3px] rounded-lg gap-0.5 |
| TabsTrigger (vertical) | px-3 py-1.5 rounded-md text-left |
<Tabs defaultValue="account" orientation="vertical">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger>
</TabsList>
<TabsContent value="account">Account settings here.</TabsContent>
<TabsContent value="password">Password settings here.</TabsContent>
</Tabs>
States
| State | Styling |
|---|---|
| Active (default) | bg-background shadow-sm text-foreground |
| Active (line) | text-foreground + 2px primary underline |
| Inactive | bg-transparent text-muted-foreground |
| Hover | Subtle text color shift toward foreground |
| Focus | ring-[3px] ring-ring/50 |
| Disabled | text-muted-foreground opacity-50 pointer-events-none |
Icons
Size: size-4 (16px)
Spacing: Automatic gap-2 (8px between icon and text) when icon is a direct child of TabsTrigger
<TabsTrigger value="preview">
<AppWindowIcon />
Preview
</TabsTrigger>
<TabsTrigger value="code">
<CodeIcon />
Code
</TabsTrigger>
Icons work with both the default and line variants.
Badge
Style: h-5 min-w-5 px-1 rounded-full bg-primary text-xs font-bold text-primary-foreground
<TabsTrigger value="notifications">
Notifications
<Badge className="h-5 min-w-5 rounded-full">8</Badge>
</TabsTrigger>
Common Patterns
Basic Tabs (Default Variant)
<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">Account settings here.</TabsContent>
<TabsContent value="password">Password settings here.</TabsContent>
</Tabs>
Line Tabs
<Tabs defaultValue="overview">
<TabsList variant="line">
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
</TabsList>
<TabsContent value="overview">Overview content</TabsContent>
<TabsContent value="analytics">Analytics content</TabsContent>
</Tabs>
Vertical Tabs
<Tabs defaultValue="account" orientation="vertical" className="flex gap-4">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger>
</TabsList>
<div className="flex-1">
<TabsContent value="account">Account content</TabsContent>
<TabsContent value="password">Password content</TabsContent>
<TabsContent value="notifications">Notifications content</TabsContent>
</div>
</Tabs>
With Icons
<Tabs defaultValue="preview">
<TabsList>
<TabsTrigger value="preview">
<AppWindowIcon />
Preview
</TabsTrigger>
<TabsTrigger value="code">
<CodeIcon />
Code
</TabsTrigger>
</TabsList>
<TabsContent value="preview">Preview content</TabsContent>
<TabsContent value="code">Code content</TabsContent>
</Tabs>
With Badge
<Tabs defaultValue="billing">
<TabsList>
<TabsTrigger value="billing">Billing</TabsTrigger>
<TabsTrigger value="notifications">
Notifications
<Badge className="h-5 min-w-5 rounded-full">8</Badge>
</TabsTrigger>
</TabsList>
</Tabs>
With Disabled Tab
<Tabs defaultValue="home">
<TabsList>
<TabsTrigger value="home">Home</TabsTrigger>
<TabsTrigger value="settings" disabled>Settings</TabsTrigger>
</TabsList>
<TabsContent value="home">Home content</TabsContent>
</Tabs>
Tabs with Cards
Content sections organized by tabs, with each panel containing a Card.
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Overview</CardTitle>
</CardHeader>
<CardContent>
{/* Content */}
</CardContent>
</Card>
</TabsContent>
{/* Other tab contents */}
</Tabs>
Spacing
- TabsList to TabsContent:
mt-2(built into TabsContent) - Cards within tabs:
space-y-4
Controlled Tabs
const [activeTab, setActiveTab] = useState("account")
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
</Tabs>
Variant Comparison
| Feature | Default (Pill) | Line |
|---|---|---|
| Background container | bg-muted rounded-lg | Transparent with border-b |
| Active indicator | White pill with shadow | 2px underline in primary color |
| Supports vertical | Yes | Yes |
| Supports icons | Yes | Yes |
| Supports badges | Yes | Yes |
| Best for | Standalone sections, settings | Page-level navigation, content areas |
Accessibility
- Built on Radix Tabs — handles keyboard and focus automatically
- Keyboard: Arrow Left/Right between horizontal tabs, Arrow Up/Down for vertical tabs, Tab to enter panel content
- Screen reader: announces
tabrole, selected state, and panel association - Uses
tabpanelrole witharia-labelledbylinking to tab trigger - Automatic focus management with roving tabindex
- Supports
dir="rtl"for right-to-left languages
Gotchas
| Problem | Solution |
|---|---|
| Content not showing | Ensure value matches defaultValue or controlled value |
| Tabs not filling width | TabsTrigger uses flex-1 by default in the default variant |
| Icon alignment | Icons are auto-spaced via gap-2 — no extra class needed |
| Badge spacing | Place Badge directly inside TabsTrigger — gap is automatic |
| Want underline style, not pill | Use variant="line" on TabsList |
| Vertical not working | Add orientation="vertical" on the Tabs root, not TabsList |
| Arrow keys go wrong direction in vertical | Radix automatically switches to Up/Down arrows when vertical |
See Also
- Accessibility: Navigation Accessibility Requirements - Keyboard navigation, ARIA tab patterns
- Related Components: Accordion (for stacked content)
- Patterns: Component Match
- Reference: shadcn/ui Tabs, Radix Tabs API
Last updated: February 20, 2026