Tabsv1.0.7Documentation Under Review
Interactive tab navigation for organizing content into separate views with accessible keyboard navigation and state management.
Tabs
Interactive tab navigation component built on Radix UI primitives with full keyboard navigation support and WCAG 2.1 AA compliance. Organize content into separate views with a clean, accessible tab interface.
Installation
Install the Tabs component from the Catalyst component library:
npm install @pmi/catalyst-tabsExamples
Basic Usage
Create a simple tabbed interface with multiple content panels:
Overview
View your account overview, recent activity, and key metrics at a glance.
Disabled State
Disable all tabs to prevent interaction during loading or processing states:
Content is disabled while processing...
Custom Styling
Customize tab appearance using dual className props with aqua theme:
Aqua-themed tab content showing custom colors
Controlled Tabs
Use controlled state for programmatic tab switching and state tracking:
Update your personal information and profile details.
Custom Tab Styling
Customize tab list appearance with additional styling:
Welcome to the home page with custom styled tabs.
Advanced Examples
Data Table with Filters
Use tabs to filter table data by status:
| Order ID | Customer | Status | Amount |
|---|---|---|---|
| ORD-001 | Alice Johnson | pending | $129.99 |
| ORD-002 | Bob Smith | completed | $89.50 |
| ORD-003 | Carol White | pending | $199.00 |
| ORD-004 | David Brown | completed | $45.00 |
| ORD-005 | Eve Davis | cancelled | $75.25 |
Feature Comparison Table
Compare product features across different plans using tabs:
Basic
$9/month
- ✓5 Projects
- ✓10 GB Storage
- ✓Email Support
- ✓Basic Analytics
API Reference
TabsRoot
The root container component that manages tab state and coordination between triggers and content panels.
Prop
Type
TabsList
Container for tab triggers that displays the clickable tab buttons.
Prop
Type
TabsTrigger
Individual tab button that activates its associated content panel.
Prop
Type
TabsContent
Content panel associated with a specific tab trigger.
Prop
Type
Inherited Props
All components inherit additional props from their respective Radix UI primitives:
- TabsRoot: Inherits from
Radix.Tabs.Root - TabsList: Inherits from
Radix.Tabs.List - TabsTrigger: Inherits from
Radix.Tabs.Trigger - TabsContent: Inherits from
Radix.Tabs.Content
See Radix UI Tabs documentation for complete inherited prop details.
Compound Component Pattern
The Tabs component uses a compound component pattern where four related components work together:
<TabsRoot>
{/* Container managing state */}
<TabsList>
{/* Tab buttons container */}
<TabsTrigger value="tab1">Label</TabsTrigger>
<TabsTrigger value="tab2">Label</TabsTrigger>
</TabsList>
{/* Content panels */}
<TabsContent value="tab1">Content 1</TabsContent>
<TabsContent value="tab2">Content 2</TabsContent>
</TabsRoot>Key Concepts:
- Value Association:
TabsTriggerandTabsContentare linked via matchingvalueprops - State Management:
TabsRootcontrols which tab is active - Automatic ARIA: Radix UI handles all accessibility attributes
- Flexible Layout: Components can be arranged in various layouts
Advanced Patterns
asChild Composition
Use the asChild prop to render custom wrapper elements while preserving tab functionality:
// Custom navigation wrapper for TabsList
<TabsList asChild>
<nav role="tablist" className="custom-nav">
<TabsTrigger value="home">Home</TabsTrigger>
<TabsTrigger value="about">About</TabsTrigger>
</nav>
</TabsList>
// Custom button wrapper for TabsTrigger
<TabsTrigger asChild value="settings">
<button className="custom-button">
<SettingsIcon /> Settings
</button>
</TabsTrigger>The asChild prop uses Radix UI's Slot component to merge props with the child element.
Dual className Pattern
TabsTrigger accepts two className props for layered styling:
<TabsTrigger
value="tab1"
className="border-styles layout-styles" // Outer container
classNameInner="text-styles background-styles" // Inner wrapper
>
Tab Label
</TabsTrigger>Styling Layers:
- className: Controls border, layout, and active state indicators
- classNameInner: Controls text, background, hover, and focus styles
Example - Aqua Theme:
<TabsTrigger
value="tab1"
className="data-[state=active]:border-b-pmi-aqua-500 data-[state=inactive]:border-b-neutral-300"
classNameInner="group-hover:bg-pmi-aqua-100 group-focus-visible:ring-pmi-aqua-500"
>
Aqua Theme Tab
</TabsTrigger>Controlled State Management
Use controlled state for complex logic, form integration, or URL synchronization:
const [activeTab, setActiveTab] = useState('overview');
// Programmatically change tabs
const navigateToSettings = () => setActiveTab('settings');
// Track tab history
const [history, setHistory] = useState<string[]>([]);
const handleTabChange = (value: string) => {
setActiveTab(value);
setHistory([...history, value]);
};
<TabsRoot value={activeTab} onValueChange={handleTabChange}>
{/* tabs */}
</TabsRoot>;Uncontrolled State (Simpler)
Use uncontrolled state with defaultValue when you don't need programmatic control:
<TabsRoot defaultValue="overview">{/* tabs - Radix UI manages state internally */}</TabsRoot>Accessibility
The Tabs component is built on Radix UI primitives and provides comprehensive keyboard navigation and screen reader support, meeting WCAG 2.1 AA standards.
Keyboard Navigation
| Key | Action |
|---|---|
| Tab | Move focus to the active tab or first tab |
| Arrow Left | Activate and focus the previous tab (horizontal) |
| Arrow Right | Activate and focus the next tab (horizontal) |
| Arrow Up | Activate and focus the previous tab (vertical) |
| Arrow Down | Activate and focus the next tab (vertical) |
| Home | Activate and focus the first tab |
| End | Activate and focus the last tab |
| Enter / Space | Activate focused tab (when activationMode="manual") |
Automatic vs Manual Activation:
- Automatic (default): Arrow keys immediately activate tabs
- Manual: Arrow keys focus tabs, Enter/Space activates them
Screen Reader Support
The component provides full screen reader support with automatic ARIA attribute management:
Tab States Announced:
- "Tab [name], selected" - Active tab
- "Tab [name]" - Inactive tab
- "Tab [name], disabled" - Disabled tab
- "[number] of [total] tabs" - Position information
Content Panels:
- Associated with trigger via
aria-labelledby - Only visible panel announced
- Smooth transitions between panels
ARIA Attributes
Radix UI automatically manages all required ARIA attributes:
TabsList:
role="tablist"- Identifies the tab list containeraria-orientation="horizontal"or"vertical"- Indicates tab orientation
TabsTrigger:
role="tab"- Identifies each tab buttonaria-selected="true"or"false"- Indicates active statearia-controls="[content-id]"- Associates trigger with contentaria-disabled="true"- When disabled prop is truetabindex="0"(active) or"-1"(inactive) - Focus management
TabsContent:
role="tabpanel"- Identifies the content panelaria-labelledby="[trigger-id]"- Associates content with triggerhidden- Hides inactive panels from assistive technologies
Focus Management
Visible Focus Indicators:
- 2px solid inset ring on focus:
ring-2 ring-inset - Default color:
ring-pmi-off-black-800(light),ring-white(dark) - Custom themes can override:
group-focus-visible:ring-[color]
Focus Behavior:
- Only one tab trigger is focusable at a time (roving tabindex)
- Focus automatically moves when arrow keys change active tab
- Focus persists on tab trigger, not content panel
:focus-visibleused to show focus only for keyboard navigation
WCAG Compliance:
- Focus indicators meet 3:1 contrast ratio requirement
- Focus visible on all interactive elements
- No focus traps or keyboard navigation issues
Color Contrast
All text and interactive elements meet WCAG 2.1 AA contrast requirements:
Text Contrast:
- Inactive tabs:
text-pmi-off-black-600(≥4.5:1 contrast) - Active tabs:
text-pmi-off-black-800(≥7:1 contrast) - Dark mode inactive:
text-pmi-off-black-200(≥4.5:1 contrast) - Dark mode active:
text-white(≥7:1 contrast)
Border Contrast:
- Active border: 2px
border-b-pmi-off-black-800(≥3:1 contrast) - Inactive border: 1px
border-b-pmi-neutral-200(≥3:1 contrast) - Dark mode active:
border-b-white(≥3:1 contrast)
Disabled State:
- 40% opacity:
opacity-40 - Maintains sufficient contrast for visibility
- Pointer events disabled:
pointer-events-none
Best Practices
- Always Provide Labels: Ensure every tab trigger has clear, descriptive text
- Unique Values: Use unique
valueprops for each tab trigger/content pair - Logical Order: Arrange tabs in a logical, predictable order
- Visible Content: Ensure tab content is visible and not cut off
- Loading States: Show loading indicators when switching to tabs with async content
- Error Handling: Display error messages within TabsContent when data fails to load
- Mobile Consideration: Test tab overflow on small screens, consider horizontal scroll or dropdown
Design Tokens
The Tabs component uses Catalyst design tokens for consistent theming across light and dark modes.
Colors
Border Colors:
// Active state (2px border)
border - b - pmi - off - black - 800; // Light mode active
dark: border - b - white; // Dark mode active
// Inactive state (1px border)
border - b - pmi - neutral - 200; // Light mode inactive
dark: border - b - pmi - off - black - 600; // Dark mode inactiveText Colors:
// Inactive text
text-pmi-off-black-600 // Light mode inactive
dark:text-pmi-off-black-200 // Dark mode inactive
// Active text
group-data-[state=active]:text-pmi-off-black-800 // Light mode active
dark:group-data-[state=active]:text-white // Dark mode activeHover Colors:
group-hover:bg-pmi-violet-100 // Light mode hover background
dark:group-hover:bg-pmi-violet-600 // Dark mode hover backgroundFocus Colors:
group-focus-visible:ring-pmi-off-black-800 // Light mode focus ring
dark:group-focus-visible:ring-white // Dark mode focus ringCustom Theme - Aqua
Product variant includes an aqua theme example:
// Aqua theme border colors
className =
'data-[state=active]:border-b-pmi-aqua-500 data-[state=inactive]:border-b-neutral-300 dark:data-[state=active]:border-b-pmi-aqua-500 dark:data-[state=inactive]:border-b-neutral-700';
// Aqua theme hover/focus colors
classNameInner =
'group-hover:bg-pmi-aqua-100 dark:group-hover:bg-pmi-aqua-700 group-focus-visible:ring-pmi-aqua-500 dark:group-focus-visible:ring-pmi-aqua-500';Spacing
Padding:
px-1.5 py-1 // TabsTrigger outer padding (6px horizontal, 4px vertical)
p-1 // TabsTrigger inner padding (4px all sides)Border Width:
border - b; // 1px inactive border
border - b - 2; // 2px active borderFocus Ring:
ring - 2; // 2px ring width
ring - inset; // Inset ring positioningTransitions
transition; // Smooth transitions for all state changesAll color, border, and background changes use CSS transitions for smooth visual feedback.
See Also
- Form - Multi-step forms with tab navigation
- Card - Common container for tab content panels
- Button - Custom tab trigger styling
- Badge - Tab labels with notification counts
- Accordion - Alternative vertical navigation pattern
- Tabs - Marketing variant with same functionality
Component Status: Stable
Package: @pmi/[email protected]
Radix Primitive: @radix-ui/[email protected]
Sticky Nav
A flexible navigation component that sticks to the top of the viewport, built with Radix UI Navigation Menu primitives. Supports tab-based navigation with active states, gradient styling, and responsive behavior.
Sidebar
A collapsible navigation sidebar component with support for headers, menus, submenus, and footers. Built with accessibility and keyboard navigation in mind.