Skip to content

VerticalTabs

A flexible vertical tab component for side-by-side navigation. Perfect for settings pages, dashboards, and content-heavy interfaces where horizontal space is available.

Overview

VerticalTabs provides a compound component pattern for building vertical tab interfaces. Unlike horizontal tabs that stack content below, vertical tabs place navigation on the left and content on the right, making better use of wide screens.

Key Features

  • 📐 Vertical Layout: Side-by-side navigation and content
  • 🎨 Rich Content: Support for icons, descriptions, and custom styling
  • ✨ Smooth Transitions: Animated content switching
  • ♿ Accessible: Full ARIA support and keyboard navigation
  • 🎯 Active States: Clear visual feedback for selected tab
  • 🔄 Controlled: Optional onChange callback for external state management

When to Use

Use VerticalTabs when:

Settings pages - Multiple configuration sections
Profile pages - Different user information categories
Dashboards - Switching between different views
Wide layouts - Horizontal space is available
Rich navigation - Tabs need icons and descriptions

Use horizontal tabs instead when:

Mobile-first - Limited horizontal space
Simple navigation - Few tabs without descriptions
Narrow layouts - Vertical space is premium


Quick Start

Basic Example

tsx
import {
  VerticalTabs,
  VerticalTabsList,
  VerticalTabTrigger,
  VerticalTabContent,
} from '@/shared/ui/components/vertical-tabs/VerticalTabs'

export function SettingsPage() {
  return (
    <VerticalTabs defaultValue="profile">
      <VerticalTabsList>
        <VerticalTabTrigger value="profile">
          Profile
        </VerticalTabTrigger>
        <VerticalTabTrigger value="account">
          Account
        </VerticalTabTrigger>
        <VerticalTabTrigger value="security">
          Security
        </VerticalTabTrigger>
      </VerticalTabsList>

      <VerticalTabContent value="profile">
        <h2>Profile Settings</h2>
        {/* Profile form */}
      </VerticalTabContent>

      <VerticalTabContent value="account">
        <h2>Account Settings</h2>
        {/* Account form */}
      </VerticalTabContent>

      <VerticalTabContent value="security">
        <h2>Security Settings</h2>
        {/* Security form */}
      </VerticalTabContent>
    </VerticalTabs>
  )
}

Component Structure

Compound Components

VerticalTabs uses a compound component pattern:

tsx
<VerticalTabs>              {/* Root container with state */}
  <VerticalTabsList>        {/* Left sidebar navigation */}
    <VerticalTabTrigger />  {/* Individual tab button */}
    <VerticalTabTrigger />
  </VerticalTabsList>
  
  <VerticalTabContent />    {/* Right content panel */}
  <VerticalTabContent />
</VerticalTabs>

Features in Detail

1. With Icons

Add icons to make tabs more recognizable:

tsx
import { User, Settings, Shield, Bell } from 'lucide-react'

<VerticalTabs defaultValue="profile">
  <VerticalTabsList>
    <VerticalTabTrigger value="profile" icon={<User className="h-4 w-4" />}>
      Profile
    </VerticalTabTrigger>
    <VerticalTabTrigger value="settings" icon={<Settings className="h-4 w-4" />}>
      Settings
    </VerticalTabTrigger>
    <VerticalTabTrigger value="security" icon={<Shield className="h-4 w-4" />}>
      Security
    </VerticalTabTrigger>
    <VerticalTabTrigger value="notifications" icon={<Bell className="h-4 w-4" />}>
      Notifications
    </VerticalTabTrigger>
  </VerticalTabsList>

  {/* Content panels */}
</VerticalTabs>

2. With Descriptions

Add descriptions for additional context:

tsx
<VerticalTabsList>
  <VerticalTabTrigger
    value="profile"
    icon={<User className="h-4 w-4" />}
    description="Manage your personal information"
  >
    Profile
  </VerticalTabTrigger>
  <VerticalTabTrigger
    value="account"
    icon={<Settings className="h-4 w-4" />}
    description="Update account preferences"
  >
    Account
  </VerticalTabTrigger>
  <VerticalTabTrigger
    value="security"
    icon={<Shield className="h-4 w-4" />}
    description="Password and authentication"
  >
    Security
  </VerticalTabTrigger>
</VerticalTabsList>

3. Controlled State

Control the active tab externally:

tsx
function ControlledTabs() {
  const [activeTab, setActiveTab] = useState('profile')

  return (
    <>
      <div className="mb-4">
        <p>Current tab: {activeTab}</p>
        <Button onClick={() => setActiveTab('security')}>
          Jump to Security
        </Button>
      </div>

      <VerticalTabs
        defaultValue={activeTab}
        onChange={(value) => {
          setActiveTab(value)
          // Track analytics, save to URL, etc.
        }}
      >
        {/* Tabs */}
      </VerticalTabs>
    </>
  )
}

4. Custom Styling

Customize the appearance:

tsx
<VerticalTabs defaultValue="profile" className="max-w-5xl mx-auto">
  <VerticalTabsList className="w-64 bg-muted/30 rounded-lg p-2">
    <VerticalTabTrigger
      value="profile"
      className="hover:bg-accent"
    >
      Profile
    </VerticalTabTrigger>
  </VerticalTabsList>

  <VerticalTabContent
    value="profile"
    className="bg-card rounded-lg p-6"
  >
    {/* Content */}
  </VerticalTabContent>
</VerticalTabs>

Common Patterns

Settings Page

tsx
import { User, Lock, Bell, CreditCard, Users } from 'lucide-react'

function UserSettings() {
  return (
    <div className="container max-w-6xl py-8">
      <h1 className="text-3xl font-bold mb-8">Settings</h1>
      
      <VerticalTabs defaultValue="profile">
        <VerticalTabsList className="w-64">
          <VerticalTabTrigger
            value="profile"
            icon={<User className="h-4 w-4" />}
            description="Personal information"
          >
            Profile
          </VerticalTabTrigger>
          <VerticalTabTrigger
            value="security"
            icon={<Lock className="h-4 w-4" />}
            description="Password & 2FA"
          >
            Security
          </VerticalTabTrigger>
          <VerticalTabTrigger
            value="notifications"
            icon={<Bell className="h-4 w-4" />}
            description="Email preferences"
          >
            Notifications
          </VerticalTabTrigger>
          <VerticalTabTrigger
            value="billing"
            icon={<CreditCard className="h-4 w-4" />}
            description="Plans & payment"
          >
            Billing
          </VerticalTabTrigger>
          <VerticalTabTrigger
            value="team"
            icon={<Users className="h-4 w-4" />}
            description="Manage members"
          >
            Team
          </VerticalTabTrigger>
        </VerticalTabsList>

        <VerticalTabContent value="profile">
          <ProfileSettings />
        </VerticalTabContent>
        <VerticalTabContent value="security">
          <SecuritySettings />
        </VerticalTabContent>
        <VerticalTabContent value="notifications">
          <NotificationSettings />
        </VerticalTabContent>
        <VerticalTabContent value="billing">
          <BillingSettings />
        </VerticalTabContent>
        <VerticalTabContent value="team">
          <TeamSettings />
        </VerticalTabContent>
      </VerticalTabs>
    </div>
  )
}

Dashboard Views

tsx
import { BarChart, Users, DollarSign, TrendingUp } from 'lucide-react'

function DashboardPage() {
  return (
    <VerticalTabs defaultValue="overview">
      <VerticalTabsList>
        <VerticalTabTrigger
          value="overview"
          icon={<BarChart className="h-4 w-4" />}
        >
          Overview
        </VerticalTabTrigger>
        <VerticalTabTrigger
          value="users"
          icon={<Users className="h-4 w-4" />}
        >
          Users
        </VerticalTabTrigger>
        <VerticalTabTrigger
          value="revenue"
          icon={<DollarSign className="h-4 w-4" />}
        >
          Revenue
        </VerticalTabTrigger>
        <VerticalTabTrigger
          value="analytics"
          icon={<TrendingUp className="h-4 w-4" />}
        >
          Analytics
        </VerticalTabTrigger>
      </VerticalTabsList>

      <VerticalTabContent value="overview">
        <DashboardOverview />
      </VerticalTabContent>
      {/* Other content panels */}
    </VerticalTabs>
  )
}

Best Practices

✅ Do's

  • Use descriptive labels - Clear, concise tab names
  • Add icons - Improve scannability and recognition
  • Provide descriptions - Help users understand tab content
  • Limit tab count - 5-8 tabs maximum for usability
  • Group related content - Logical organization
  • Use consistent widths - Fixed sidebar width for stability

❌ Don'ts

  • Don't use on mobile - Not enough horizontal space
  • Don't nest tabs - Avoid tabs within tabs
  • Don't use for navigation - Use for content switching, not site navigation
  • Don't forget accessibility - Ensure keyboard navigation works
  • Don't overload tabs - Too many tabs overwhelm users

Accessibility

The component includes full accessibility support:

  • ARIA roles: tablist, tab, tabpanel
  • ARIA attributes: aria-selected, aria-controls, aria-labelledby
  • Keyboard navigation: Arrow keys, Tab, Enter
  • Focus management: Clear focus indicators
  • Screen reader support: Proper announcements
tsx
// Accessibility is built-in
<VerticalTabs defaultValue="profile">
  <VerticalTabsList>
    {/* role="tablist" aria-orientation="vertical" */}
    <VerticalTabTrigger value="profile">
      {/* role="tab" aria-selected aria-controls */}
      Profile
    </VerticalTabTrigger>
  </VerticalTabsList>

  <VerticalTabContent value="profile">
    {/* role="tabpanel" aria-labelledby */}
    Content
  </VerticalTabContent>
</VerticalTabs>

Animation

Content panels animate in smoothly:

  • Fade in: Opacity transition
  • Slide in: Subtle right-to-left slide
  • Duration: 200ms for snappy feel

The animation is handled automatically using Tailwind's animate-in utilities.


API Reference

For detailed prop documentation, see:



See Also