ComponentsLayout
Tabs
Tabbed interface for organizing content into switchable panels
The Tabs component organizes content into switchable panels, allowing users to view one section at a time. Built on Radix UI Tabs, it provides full keyboard navigation and ARIA support. It uses a compound component pattern with TabsList, TabsTrigger, and TabsContent sub-components.
Import
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@nim-ui/components';Basic Usage
Content for Tab 1. This is the first panel.
Code
<Tabs defaultValue="tab1">
<TabsList>
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
<TabsTrigger value="tab3">Tab 3</TabsTrigger>
</TabsList>
<TabsContent value="tab1">
<p>Content for Tab 1.</p>
</TabsContent>
<TabsContent value="tab2">
<p>Content for Tab 2.</p>
</TabsContent>
<TabsContent value="tab3">
<p>Content for Tab 3.</p>
</TabsContent>
</Tabs>With Rich Content
Product Overview
A comprehensive solution for modern web development. Built with performance and developer experience in mind.
Code
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="features">Features</TabsTrigger>
<TabsTrigger value="pricing">Pricing</TabsTrigger>
</TabsList>
<TabsContent value="overview">
<h3>Product Overview</h3>
<p>A comprehensive solution for modern web development.</p>
</TabsContent>
<TabsContent value="features">
<h3>Key Features</h3>
<ul>
<li>TypeScript support</li>
<li>Accessible by default</li>
<li>Dark mode built-in</li>
<li>Responsive design</li>
</ul>
</TabsContent>
<TabsContent value="pricing">
<h3>Pricing Plans</h3>
<p>Free for open source. Enterprise plans available.</p>
</TabsContent>
</Tabs>Disabled Tab
Individual tabs can be disabled to prevent selection.
This tab is active.
Code
<Tabs defaultValue="active">
<TabsList>
<TabsTrigger value="active">Active</TabsTrigger>
<TabsTrigger value="disabled" disabled>Disabled</TabsTrigger>
<TabsTrigger value="another">Another</TabsTrigger>
</TabsList>
<TabsContent value="active">This tab is active.</TabsContent>
<TabsContent value="another">This tab is also available.</TabsContent>
</Tabs>Controlled Tabs
Control the active tab externally using React state.
function ControlledTabs() {
const [activeTab, setActiveTab] = useState('tab1');
return (
<div>
<p>Current tab: {activeTab}</p>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
<TabsTrigger value="tab3">Tab 3</TabsTrigger>
</TabsList>
<TabsContent value="tab1">Content 1</TabsContent>
<TabsContent value="tab2">Content 2</TabsContent>
<TabsContent value="tab3">Content 3</TabsContent>
</Tabs>
<Button onClick={() => setActiveTab('tab2')}>
Go to Tab 2
</Button>
</div>
);
}Component Architecture
| Component | Description |
|---|---|
Tabs | Root component that manages active tab state (Radix Tabs.Root) |
TabsList | Container for tab triggers, styled as a pill bar |
TabsTrigger | Clickable tab button that activates a panel (Radix Tabs.Trigger) |
TabsContent | Panel content shown when its corresponding trigger is active (Radix Tabs.Content) |
Props
Tabs (Root)
| Name | Type | Default | Description |
|---|---|---|---|
defaultValue | string | - | The value of the tab to select by default (uncontrolled) |
value | string | - | The controlled active tab value |
onValueChange | (value: string) => void | - | Callback when the active tab changes |
orientation | 'horizontal' | 'vertical' | 'horizontal' | Orientation of the tab list for keyboard navigation |
dir | 'ltr' | 'rtl' | - | Reading direction for keyboard navigation |
activationMode | 'automatic' | 'manual' | 'automatic' | Whether tabs activate on focus (automatic) or on click (manual) |
className | string | - | Additional CSS classes to apply to the root |
children* | ReactNode | - | TabsList and TabsContent components |
TabsList
| Name | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS classes to apply to the tab list container |
children* | ReactNode | - | TabsTrigger components |
TabsTrigger
| Name | Type | Default | Description |
|---|---|---|---|
value* | string | - | Unique value that links this trigger to its corresponding TabsContent |
disabled | boolean | false | Whether the tab trigger is disabled |
className | string | - | Additional CSS classes to apply |
children* | ReactNode | - | Tab label content |
TabsContent
| Name | Type | Default | Description |
|---|---|---|---|
value* | string | - | Value that links this content panel to its corresponding TabsTrigger |
forceMount | boolean | - | Force the content to mount even when inactive (useful for animations) |
className | string | - | Additional CSS classes to apply |
children* | ReactNode | - | Tab panel content |
Usage Examples
Settings Page
function SettingsPage() {
return (
<Container maxWidth="lg">
<h1 className="text-2xl font-bold mb-6">Settings</h1>
<Tabs defaultValue="profile">
<TabsList>
<TabsTrigger value="profile">Profile</TabsTrigger>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger>
<TabsTrigger value="billing">Billing</TabsTrigger>
</TabsList>
<TabsContent value="profile">
<Card>
<CardHeader>
<h2 className="text-lg font-semibold">Profile Settings</h2>
</CardHeader>
<CardContent>
<Stack spacing="md">
<Input label="Display Name" />
<Textarea label="Bio" rows={3} />
<Button variant="primary">Save Profile</Button>
</Stack>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="account">
<Card>
<CardContent>Account settings content</CardContent>
</Card>
</TabsContent>
<TabsContent value="notifications">
<Card>
<CardContent>Notification preferences</CardContent>
</Card>
</TabsContent>
<TabsContent value="billing">
<Card>
<CardContent>Billing information</CardContent>
</Card>
</TabsContent>
</Tabs>
</Container>
);
}Product Details
function ProductDetails({ product }) {
return (
<Tabs defaultValue="description">
<TabsList>
<TabsTrigger value="description">Description</TabsTrigger>
<TabsTrigger value="specs">Specifications</TabsTrigger>
<TabsTrigger value="reviews">
Reviews ({product.reviewCount})
</TabsTrigger>
</TabsList>
<TabsContent value="description">
<div className="prose">
<p>{product.description}</p>
</div>
</TabsContent>
<TabsContent value="specs">
<Table
data={product.specs}
columns={[
{ key: 'name', label: 'Specification' },
{ key: 'value', label: 'Value' },
]}
/>
</TabsContent>
<TabsContent value="reviews">
<Stack spacing="md">
{product.reviews.map((review) => (
<Card key={review.id}>
<CardContent>
<p className="font-semibold">{review.author}</p>
<p>{review.text}</p>
</CardContent>
</Card>
))}
</Stack>
</TabsContent>
</Tabs>
);
}Dashboard with Tabs
function DashboardTabs() {
return (
<Tabs defaultValue="analytics">
<Flex justify="between" align="center">
<TabsList>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
<TabsTrigger value="logs">Logs</TabsTrigger>
</TabsList>
<Button variant="outline" size="sm">Export</Button>
</Flex>
<TabsContent value="analytics">
<Grid cols={3} gap="md">
<Card><CardContent>Chart 1</CardContent></Card>
<Card><CardContent>Chart 2</CardContent></Card>
<Card><CardContent>Chart 3</CardContent></Card>
</Grid>
</TabsContent>
<TabsContent value="reports">
<p>Reports content here</p>
</TabsContent>
<TabsContent value="logs">
<p>System logs here</p>
</TabsContent>
</Tabs>
);
}Accessibility
The Tabs component is built on Radix UI Tabs and follows the WAI-ARIA Tabs Pattern:
- ARIA roles: Automatically applies
role="tablist",role="tab", androle="tabpanel" - ARIA attributes: Sets
aria-selected,aria-controls, andaria-labelledbyon the appropriate elements - Focus management: Focus moves between tabs using arrow keys within the tab list
- Activation modes: Supports both automatic (activate on focus) and manual (activate on Enter/Space) modes
Keyboard Support
| Key | Action |
|---|---|
| Arrow Right | Move focus to next tab (horizontal orientation) |
| Arrow Left | Move focus to previous tab (horizontal orientation) |
| Arrow Down | Move focus to next tab (vertical orientation) |
| Arrow Up | Move focus to previous tab (vertical orientation) |
| Home | Move focus to first tab |
| End | Move focus to last tab |
| Enter / Space | Activate the focused tab (manual activation mode) |
| Tab | Move focus into the active tab panel |
Best Practices
- Use descriptive tab labels that clearly indicate the panel content
- Keep the number of tabs reasonable (typically 2-7 tabs)
- Ensure tab content is logically grouped
- Consider
activationMode="manual"when tab switching triggers expensive operations