Skip to main content

Overview

The Tabs component provides an accessible way to organize content into separate views. Built on React Aria Components with full keyboard navigation and multiple visual variants.

Installation

import {
  Tabs,
  TabList,
  Tab,
  TabPanel,
} from "@/components/ui/Tabs";

Anatomy

The Tabs component consists of four sub-components:
  • Tabs: Root container managing tab state (uses React Aria’s Tabs)
  • TabList: Container for tab buttons (uses React Aria’s TabList)
  • Tab: Individual tab button (uses React Aria’s Tab)
  • TabPanel: Content panel for each tab (uses React Aria’s TabPanel)

Basic Usage

<Tabs defaultSelectedKey="tab1">
  <TabList>
    <Tab id="tab1">Overview</Tab>
    <Tab id="tab2">Analytics</Tab>
    <Tab id="tab3">Settings</Tab>
  </TabList>
  <TabPanel id="tab1">
    <div className="p-4">
      <h3>Overview</h3>
      <p>Welcome to the overview section.</p>
    </div>
  </TabPanel>
  <TabPanel id="tab2">
    <div className="p-4">
      <h3>Analytics</h3>
      <p>View detailed analytics and metrics.</p>
    </div>
  </TabPanel>
  <TabPanel id="tab3">
    <div className="p-4">
      <h3>Settings</h3>
      <p>Manage your settings here.</p>
    </div>
  </TabPanel>
</Tabs>

Variants

The Tabs component supports four visual variants:

Default

Standard tabs with underline selection indicator.
<Tabs defaultSelectedKey="tab1">
  <TabList variant="default">
    <Tab id="tab1">Dashboard</Tab>
    <Tab id="tab2">Reports</Tab>
    <Tab id="tab3">Settings</Tab>
  </TabList>
  <TabPanel id="tab1">
    <div className="p-4">Dashboard content</div>
  </TabPanel>
  {/* Additional panels */}
</Tabs>

Soft

Rounded tabs with background highlighting.
<Tabs defaultSelectedKey="tab1">
  <TabList variant="soft">
    <Tab id="tab1" variant="soft">Profile</Tab>
    <Tab id="tab2" variant="soft">Notifications</Tab>
    <Tab id="tab3" variant="soft">Security</Tab>
  </TabList>
  {/* TabPanels */}
</Tabs>

Outline

Tabs with border highlighting when selected.
<Tabs defaultSelectedKey="tab1">
  <TabList variant="outline">
    <Tab id="tab1" variant="outline">Billing</Tab>
    <Tab id="tab2" variant="outline">Domains</Tab>
    <Tab id="tab3" variant="outline">Storage</Tab>
  </TabList>
  {/* TabPanels */}
</Tabs>

Ghost

Minimal styling with subtle background on selection.
<Tabs defaultSelectedKey="tab1">
  <TabList variant="ghost">
    <Tab id="tab1" variant="ghost">Appearance</Tab>
    <Tab id="tab2" variant="ghost">Advanced</Tab>
    <Tab id="tab3" variant="ghost">Documentation</Tab>
  </TabList>
  {/* TabPanels */}
</Tabs>

Sizes

Three size options control height, padding, and text size:
{/* Small */}
<Tabs defaultSelectedKey="tab1">
  <TabList variant="soft" size="sm">
    <Tab id="tab1" variant="soft" size="sm">Tab 1</Tab>
    <Tab id="tab2" variant="soft" size="sm">Tab 2</Tab>
  </TabList>
  {/* TabPanels */}
</Tabs>

{/* Medium (default) */}
<Tabs defaultSelectedKey="tab1">
  <TabList variant="soft" size="md">
    <Tab id="tab1" variant="soft" size="md">Tab 1</Tab>
    <Tab id="tab2" variant="soft" size="md">Tab 2</Tab>
  </TabList>
  {/* TabPanels */}
</Tabs>

{/* Large */}
<Tabs defaultSelectedKey="tab1">
  <TabList variant="soft" size="lg">
    <Tab id="tab1" variant="soft" size="lg">Tab 1</Tab>
    <Tab id="tab2" variant="soft" size="lg">Tab 2</Tab>
  </TabList>
  {/* TabPanels */}
</Tabs>

Props

Tabs

orientation
string
default:"horizontal"
Layout orientation: "horizontal" | "vertical"
defaultSelectedKey
Key
Default selected tab (uncontrolled)
selectedKey
Key
Selected tab (controlled)
onSelectionChange
(key: Key) => void
Callback when selection changes
isDisabled
boolean
Disable all tabs
className
string
Additional CSS classes to apply

TabList

variant
string
default:"default"
Visual style: "default" | "soft" | "outline" | "ghost"
orientation
string
default:"horizontal"
Layout orientation: "horizontal" | "vertical"
size
string
default:"md"
Size of tabs: "sm" | "md" | "lg"
className
string
Additional CSS classes to apply

Tab

id
Key
required
Unique identifier matching TabPanel id
variant
string
default:"default"
Visual style (should match TabList variant)
size
string
default:"md"
Size (should match TabList size)
orientation
string
default:"horizontal"
Orientation (should match Tabs orientation)
isDisabled
boolean
Disable this specific tab
className
string
Additional CSS classes to apply

TabPanel

id
Key
required
Unique identifier matching Tab id
orientation
string
default:"horizontal"
Orientation (should match Tabs orientation)
className
string
Additional CSS classes to apply

Vertical Orientation

Tabs can be displayed vertically for sidebar-style navigation:
import { User, Settings, Bell, Shield } from "lucide-react";

<Tabs orientation="vertical" defaultSelectedKey="profile">
  <TabList variant="soft" orientation="vertical">
    <Tab id="profile" variant="soft" orientation="vertical">
      <User size={16} className="mr-2" />
      Profile
    </Tab>
    <Tab id="account" variant="soft" orientation="vertical">
      <Settings size={16} className="mr-2" />
      Account
    </Tab>
    <Tab id="notifications" variant="soft" orientation="vertical">
      <Bell size={16} className="mr-2" />
      Notifications
    </Tab>
    <Tab id="security" variant="soft" orientation="vertical">
      <Shield size={16} className="mr-2" />
      Security
    </Tab>
  </TabList>
  <TabPanel id="profile" orientation="vertical">
    <div className="p-6">Profile settings content</div>
  </TabPanel>
  {/* Additional panels */}
</Tabs>

Tabs with Icons

Enhance tabs with icons for better visual navigation:
import { BarChart3, FileText, Settings } from "lucide-react";

<Tabs defaultSelectedKey="dashboard">
  <TabList variant="outline">
    <Tab id="dashboard" variant="outline">
      <BarChart3 size={18} className="mr-2" />
      Dashboard
    </Tab>
    <Tab id="reports" variant="outline">
      <FileText size={18} className="mr-2" />
      Reports
    </Tab>
    <Tab id="settings" variant="outline">
      <Settings size={18} className="mr-2" />
      Settings
    </Tab>
  </TabList>
  <TabPanel id="dashboard">
    <div className="p-6">Dashboard content with charts</div>
  </TabPanel>
  {/* Additional panels */}
</Tabs>

Disabled Tabs

Disable individual tabs or all tabs:
<Tabs defaultSelectedKey="tab1">
  <TabList>
    <Tab id="tab1">Active Tab</Tab>
    <Tab id="tab2" isDisabled>Disabled Tab</Tab>
    <Tab id="tab3">Another Active Tab</Tab>
  </TabList>
  {/* TabPanels */}
</Tabs>

Controlled Tabs

Manage tab selection state externally:
const [selectedTab, setSelectedTab] = useState("tab1");

<Tabs selectedKey={selectedTab} onSelectionChange={setSelectedTab}>
  <TabList>
    <Tab id="tab1">Tab 1</Tab>
    <Tab id="tab2">Tab 2</Tab>
    <Tab id="tab3">Tab 3</Tab>
  </TabList>
  {/* TabPanels */}
</Tabs>

React Aria Components Integration

The Tabs component uses React Aria Components for:

State Management

  • Single source of truth for selected tab
  • Automatic focus management
  • Keyboard navigation handling

Accessibility

  • Proper ARIA roles and attributes
  • Keyboard navigation support
  • Screen reader announcements

Features

  • Automatic TabPanel mounting/unmounting
  • Focus restoration when tabs change
  • RTL (right-to-left) support

Accessibility Features

Keyboard Navigation

Horizontal Orientation:
  • Tab: Move focus into/out of tab list
  • Left Arrow: Select previous tab
  • Right Arrow: Select next tab
  • Home: Select first tab
  • End: Select last tab
Vertical Orientation:
  • Tab: Move focus into/out of tab list
  • Up Arrow: Select previous tab
  • Down Arrow: Select next tab
  • Home: Select first tab
  • End: Select last tab

ARIA Attributes

  • role="tablist" on TabList
  • role="tab" on Tab buttons
  • role="tabpanel" on TabPanel
  • aria-selected indicates active tab
  • aria-controls links tabs to panels
  • aria-labelledby associates panels with tabs

Screen Reader Support

  • Tab selection changes are announced
  • Disabled tabs are identified
  • Current tab position is communicated

Design Tokens

The Tabs component uses semantic design tokens:
  • --font-family-primary: Typography
  • --text-primary/secondary/tertiary: Text colors
  • --bg-primary/secondary/tertiary: Backgrounds
  • --border-primary/secondary/focus: Borders
  • --interactive-primary: Active state color
  • --transition-normal: Animation duration

Best Practices

  1. Consistent variant: Use the same variant for TabList and Tab components
  2. Meaningful labels: Use clear, concise labels that describe panel content
  3. Appropriate count: Limit to 5-7 tabs for horizontal layouts
  4. Default selection: Always set a default selected tab
  5. Loading states: Show loading indicators when tab content is async
  6. Preserve state: Consider preserving panel state when switching tabs
  7. Mobile considerations: Use vertical orientation or responsive design for mobile

Advanced Example: Settings Interface

import { User, Bell, Shield, CreditCard } from "lucide-react";

<Tabs defaultSelectedKey="profile" orientation="vertical">
  <TabList variant="soft" orientation="vertical">
    <Tab id="profile" variant="soft" orientation="vertical">
      <User size={16} className="mr-2" />
      Profile
    </Tab>
    <Tab id="notifications" variant="soft" orientation="vertical">
      <Bell size={16} className="mr-2" />
      Notifications
    </Tab>
    <Tab id="security" variant="soft" orientation="vertical">
      <Shield size={16} className="mr-2" />
      Security
    </Tab>
    <Tab id="billing" variant="soft" orientation="vertical">
      <CreditCard size={16} className="mr-2" />
      Billing
    </Tab>
  </TabList>
  <TabPanel id="profile" orientation="vertical">
    <div className="p-6">
      <h3 className="text-lg font-semibold mb-4">Profile Settings</h3>
      {/* Profile form fields */}
    </div>
  </TabPanel>
  {/* Additional panels */}
</Tabs>

Dark Mode

Tabs automatically adapt to dark mode using semantic tokens:
<Tabs defaultSelectedKey="tab1">
  <TabList variant="soft">
    <Tab id="tab1" variant="soft">Tab 1</Tab>
    <Tab id="tab2" variant="soft">Tab 2</Tab>
  </TabList>
  {/* Content adapts to theme automatically */}
</Tabs>

Build docs developers (and LLMs) love