Tabs
A set of layered sections of content — known as tab panels — that are displayed one at a time. Built on Radix UI Tabs for full keyboard navigation and accessibility.
Installation
npm install @designforge/uiUsage
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@designforge/ui";
export default function Example() {
return (
<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">Account settings go here.</TabsContent>
<TabsContent value="password">Password settings go here.</TabsContent>
</Tabs>
);
}Examples
Basic
The simplest usage with defaultValue to set the initially active tab.
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@designforge/ui";
export default function BasicTabs() {
return (
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
</TabsList>
<TabsContent value="overview">
<p>Overview content panel.</p>
</TabsContent>
<TabsContent value="analytics">
<p>Analytics content panel.</p>
</TabsContent>
<TabsContent value="reports">
<p>Reports content panel.</p>
</TabsContent>
</Tabs>
);
}Controlled
Use value and onValueChange to control the active tab from external state.
import { useState } from "react";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@designforge/ui";
export default function ControlledTabs() {
const [activeTab, setActiveTab] = useState("profile");
return (
<div>
<p className="mb-4 text-sm text-muted-foreground">
Active tab: <strong>{activeTab}</strong>
</p>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
<TabsTrigger value="profile">Profile</TabsTrigger>
<TabsTrigger value="billing">Billing</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger>
</TabsList>
<TabsContent value="profile">
<p>Manage your profile details.</p>
</TabsContent>
<TabsContent value="billing">
<p>Update your billing information.</p>
</TabsContent>
<TabsContent value="notifications">
<p>Configure notification preferences.</p>
</TabsContent>
</Tabs>
</div>
);
}Vertical
Set orientation="vertical" to stack the tab list and content side by side. Update your layout with flex to position them correctly.
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@designforge/ui";
export default function VerticalTabs() {
return (
<Tabs defaultValue="general" orientation="vertical" className="flex gap-6">
<TabsList className="flex-col h-auto">
<TabsTrigger value="general">General</TabsTrigger>
<TabsTrigger value="security">Security</TabsTrigger>
<TabsTrigger value="integrations">Integrations</TabsTrigger>
<TabsTrigger value="advanced">Advanced</TabsTrigger>
</TabsList>
<div className="flex-1">
<TabsContent value="general">General settings.</TabsContent>
<TabsContent value="security">Security settings.</TabsContent>
<TabsContent value="integrations">Third-party integrations.</TabsContent>
<TabsContent value="advanced">Advanced configuration.</TabsContent>
</div>
</Tabs>
);
}Disabled Tab
Pass disabled on a TabsTrigger to prevent interaction. The trigger is visually dimmed and skipped during keyboard navigation.
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@designforge/ui";
export default function DisabledTab() {
return (
<Tabs defaultValue="active">
<TabsList>
<TabsTrigger value="active">Active</TabsTrigger>
<TabsTrigger value="unavailable" disabled>
Unavailable
</TabsTrigger>
<TabsTrigger value="another">Another</TabsTrigger>
</TabsList>
<TabsContent value="active">This tab is active and accessible.</TabsContent>
<TabsContent value="unavailable">This content cannot be reached.</TabsContent>
<TabsContent value="another">Another accessible tab.</TabsContent>
</Tabs>
);
}With Icons
Combine icons with labels inside TabsTrigger for a richer visual hierarchy.
import { LayoutDashboard, BarChart2, FileText, Settings } from "lucide-react";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@designforge/ui";
export default function TabsWithIcons() {
return (
<Tabs defaultValue="dashboard">
<TabsList>
<TabsTrigger value="dashboard">
<LayoutDashboard className="mr-2 h-4 w-4" />
Dashboard
</TabsTrigger>
<TabsTrigger value="analytics">
<BarChart2 className="mr-2 h-4 w-4" />
Analytics
</TabsTrigger>
<TabsTrigger value="reports">
<FileText className="mr-2 h-4 w-4" />
Reports
</TabsTrigger>
<TabsTrigger value="settings">
<Settings className="mr-2 h-4 w-4" />
Settings
</TabsTrigger>
</TabsList>
<TabsContent value="dashboard">Dashboard view.</TabsContent>
<TabsContent value="analytics">Analytics view.</TabsContent>
<TabsContent value="reports">Reports view.</TabsContent>
<TabsContent value="settings">Settings view.</TabsContent>
</Tabs>
);
}Navigation-Style Layout
Override the default pill style to create an underline tab bar — common for settings pages and top-level navigation.
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@designforge/ui";
import { Card, CardContent, CardHeader, CardTitle } from "@designforge/ui";
export default function NavigationStyleTabs() {
return (
<div className="max-w-2xl mx-auto">
<h1 className="text-2xl font-bold mb-6">Account Settings</h1>
<Tabs defaultValue="profile">
<TabsList className="w-full justify-start border-b rounded-none bg-transparent px-0 mb-6">
<TabsTrigger
value="profile"
className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent shadow-none"
>
Profile
</TabsTrigger>
<TabsTrigger
value="security"
className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent shadow-none"
>
Security
</TabsTrigger>
<TabsTrigger
value="team"
className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent shadow-none"
>
Team
</TabsTrigger>
</TabsList>
<TabsContent value="profile">
<Card>
<CardHeader>
<CardTitle>Profile Information</CardTitle>
</CardHeader>
<CardContent>Edit your public profile details here.</CardContent>
</Card>
</TabsContent>
<TabsContent value="security">
<Card>
<CardHeader>
<CardTitle>Security Settings</CardTitle>
</CardHeader>
<CardContent>Manage passwords and two-factor authentication.</CardContent>
</Card>
</TabsContent>
<TabsContent value="team">
<Card>
<CardHeader>
<CardTitle>Team Members</CardTitle>
</CardHeader>
<CardContent>Invite and manage team members.</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}Lazy Loaded Content
Track which tabs have been visited and only mount their content after the first activation. This avoids rendering expensive components until they are needed.
import { useState, Suspense, lazy } from "react";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@designforge/ui";
const HeavyChart = lazy(() => import("./HeavyChart"));
export default function LazyTabs() {
const [visited, setVisited] = useState<Set<string>>(new Set(["summary"]));
const handleChange = (value: string) => {
setVisited((prev) => new Set([...prev, value]));
};
return (
<Tabs defaultValue="summary" onValueChange={handleChange}>
<TabsList>
<TabsTrigger value="summary">Summary</TabsTrigger>
<TabsTrigger value="chart">Chart</TabsTrigger>
<TabsTrigger value="raw">Raw Data</TabsTrigger>
</TabsList>
<TabsContent value="summary">
<p>Summary statistics rendered immediately.</p>
</TabsContent>
<TabsContent value="chart">
{visited.has("chart") && (
<Suspense fallback={<p>Loading chart...</p>}>
<HeavyChart />
</Suspense>
)}
</TabsContent>
<TabsContent value="raw">
{visited.has("raw") && <p>Raw data table rendered on first visit.</p>}
</TabsContent>
</Tabs>
);
}API Reference
Tabs
The root container that manages active tab state. Accepts all div props in addition to the following.
| Prop | Type | Default | Description |
|---|---|---|---|
defaultValue | string | — | The tab active on initial render (uncontrolled). |
value | string | — | The controlled active tab value. |
onValueChange | (value: string) => void | — | Callback invoked when the active tab changes. |
orientation | "horizontal" | "vertical" | "horizontal" | The orientation of the tab list; affects arrow key direction. |
className | string | — | Additional class names applied to the root element. |
TabsList
The container for all TabsTrigger elements. Renders with a muted background and handles roving tabindex for keyboard navigation.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Additional class names for the list container. |
TabsTrigger
An individual tab button. The active trigger receives a white background and a subtle box shadow.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Required. The value this trigger corresponds to. |
disabled | boolean | false | Prevents the tab from being selected. |
className | string | — | Additional class names for the trigger button. |
TabsContent
The panel shown when its corresponding trigger is active. Has a top margin and a visible focus ring.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Required. Must match the trigger's value. |
className | string | — | Additional class names for the content panel. |
Accessibility
- Built on Radix UI Tabs, which fully implements the ARIA Tabs pattern.
TabsListreceivesrole="tablist", eachTabsTriggerreceivesrole="tab", and eachTabsContentreceivesrole="tabpanel".aria-selected,aria-controls, andaria-labelledbyrelationships are managed automatically.- Keyboard navigation: Arrow keys move focus between tabs;
Home/Endjump to the first or last tab.Tabmoves focus into the active panel. - Disabled tabs receive
aria-disabled="true"and are excluded from keyboard roving. - When using
orientation="vertical", Up/Down arrow keys are used instead of Left/Right so the interaction matches the visual layout.