Tabs

PreviousNext

Tabs organize content into multiple sections and allow users to navigate between them.

Account
Make changes to your account here. Click save when you're done.
import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
  TabPanel,
  Tabs,
  TabsList,
  TabsTrigger,
} from "@/components/ui/tabs"

export function TabsDemo() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-6">
      <Tabs defaultSelectedKey="account">
        <TabsList aria-label="Account settings">
          <TabsTrigger id="account">Account</TabsTrigger>
          <TabsTrigger id="password">Password</TabsTrigger>
        </TabsList>
        <TabPanel id="account">
          <Card>
            <CardHeader>
              <CardTitle>Account</CardTitle>
              <CardDescription>
                Make changes to your account here. Click save when you&apos;re
                done.
              </CardDescription>
            </CardHeader>
            <CardContent className="grid gap-6">
              <div className="grid gap-3">
                <Label htmlFor="tabs-demo-name">Name</Label>
                <Input id="tabs-demo-name" defaultValue="Pedro Duarte" />
              </div>
              <div className="grid gap-3">
                <Label htmlFor="tabs-demo-username">Username</Label>
                <Input id="tabs-demo-username" defaultValue="@peduarte" />
              </div>
            </CardContent>
            <CardFooter>
              <Button>Save changes</Button>
            </CardFooter>
          </Card>
        </TabPanel>
        <TabPanel id="password">
          <Card>
            <CardHeader>
              <CardTitle>Password</CardTitle>
              <CardDescription>
                Change your password here. After saving, you&apos;ll be logged
                out.
              </CardDescription>
            </CardHeader>
            <CardContent className="grid gap-6">
              <div className="grid gap-3">
                <Label htmlFor="tabs-demo-current">Current password</Label>
                <Input id="tabs-demo-current" type="password" />
              </div>
              <div className="grid gap-3">
                <Label htmlFor="tabs-demo-new">New password</Label>
                <Input id="tabs-demo-new" type="password" />
              </div>
            </CardContent>
            <CardFooter>
              <Button>Save password</Button>
            </CardFooter>
          </Card>
        </TabPanel>
      </Tabs>
    </div>
  )
}

Installation

pnpm dlx shadcn@latest add tabs

Usage

import { Tabs, TabPanel, TabsList, TabsTrigger } from "@/components/ui/tabs"
<Tabs defaultSelectedKey="account">
  <TabsList aria-label="Account settings">
    <TabsTrigger id="account">Account</TabsTrigger>
    <TabsTrigger id="password">Password</TabsTrigger>
  </TabsList>
  <TabPanel id="account">
    Make changes to your account here.
  </TabPanel>
  <TabPanel id="password">
    Change your password here.
  </TabPanel>
</Tabs>

Examples

Disabled

General settings and preferences.

import {
  TabPanel,
  Tabs,
  TabsList,
  TabsTrigger,
} from "@/components/ui/tabs"

export function TabsDisabled() {
  return (
    <Tabs defaultSelectedKey="general">
      <TabsList aria-label="Settings">
        <TabsTrigger id="general">General</TabsTrigger>
        <TabsTrigger id="billing" isDisabled>
          Billing
        </TabsTrigger>
        <TabsTrigger id="team">Team</TabsTrigger>
      </TabsList>
      <TabPanel id="general">
        <p className="text-muted-foreground text-sm">
          General settings and preferences.
        </p>
      </TabPanel>
      <TabPanel id="billing">
        <p className="text-muted-foreground text-sm">
          Billing information and subscription details.
        </p>
      </TabPanel>
      <TabPanel id="team">
        <p className="text-muted-foreground text-sm">
          Manage your team members and their permissions.
        </p>
      </TabPanel>
    </Tabs>
  )
}
<Tabs defaultSelectedKey="general">
  <TabsList aria-label="Settings">
    <TabsTrigger id="general">General</TabsTrigger>
    <TabsTrigger id="billing" isDisabled>
      Billing
    </TabsTrigger>
    <TabsTrigger id="team">Team</TabsTrigger>
  </TabsList>
  <TabPanel id="general">General settings</TabPanel>
  <TabPanel id="billing">Billing information</TabPanel>
  <TabPanel id="team">Team management</TabPanel>
</Tabs>

With Icons

Manage your account settings and preferences.

import { BellIcon, LockIcon, UserIcon } from "lucide-react"

import {
  TabPanel,
  Tabs,
  TabsList,
  TabsTrigger,
} from "@/components/ui/tabs"

export function TabsIcons() {
  return (
    <Tabs defaultSelectedKey="account">
      <TabsList aria-label="Settings">
        <TabsTrigger id="account">
          <UserIcon />
          Account
        </TabsTrigger>
        <TabsTrigger id="security">
          <LockIcon />
          Security
        </TabsTrigger>
        <TabsTrigger id="notifications">
          <BellIcon />
          Notifications
        </TabsTrigger>
      </TabsList>
      <TabPanel id="account">
        <p className="text-muted-foreground text-sm">
          Manage your account settings and preferences.
        </p>
      </TabPanel>
      <TabPanel id="security">
        <p className="text-muted-foreground text-sm">
          Configure your security and privacy settings.
        </p>
      </TabPanel>
      <TabPanel id="notifications">
        <p className="text-muted-foreground text-sm">
          Choose how you want to receive notifications.
        </p>
      </TabPanel>
    </Tabs>
  )
}
import { UserIcon, LockIcon, BellIcon } from "lucide-react"
 
<Tabs defaultSelectedKey="account">
  <TabsList aria-label="Settings">
    <TabsTrigger id="account">
      <UserIcon />
      Account
    </TabsTrigger>
    <TabsTrigger id="security">
      <LockIcon />
      Security
    </TabsTrigger>
    <TabsTrigger id="notifications">
      <BellIcon />
      Notifications
    </TabsTrigger>
  </TabsList>
  <TabPanel id="account">Account settings</TabPanel>
  <TabPanel id="security">Security settings</TabPanel>
  <TabPanel id="notifications">Notification preferences</TabPanel>
</Tabs>

Orientation

Profile

Manage your profile information, avatar, and personal details.

import {
  TabPanel,
  Tabs,
  TabsList,
  TabsTrigger,
} from "@/components/ui/tabs"

export function TabsOrientation() {
  return (
    <Tabs defaultSelectedKey="profile" orientation="vertical">
      <TabsList aria-label="User settings">
        <TabsTrigger id="profile">Profile</TabsTrigger>
        <TabsTrigger id="security">Security</TabsTrigger>
        <TabsTrigger id="notifications">Notifications</TabsTrigger>
      </TabsList>
      <TabPanel id="profile">
        <div className="space-y-2">
          <h3 className="text-lg font-medium">Profile</h3>
          <p className="text-muted-foreground text-sm">
            Manage your profile information, avatar, and personal details.
          </p>
        </div>
      </TabPanel>
      <TabPanel id="security">
        <div className="space-y-2">
          <h3 className="text-lg font-medium">Security</h3>
          <p className="text-muted-foreground text-sm">
            Update your password, enable two-factor authentication, and manage
            security settings.
          </p>
        </div>
      </TabPanel>
      <TabPanel id="notifications">
        <div className="space-y-2">
          <h3 className="text-lg font-medium">Notifications</h3>
          <p className="text-muted-foreground text-sm">
            Configure how you receive notifications and updates.
          </p>
        </div>
      </TabPanel>
    </Tabs>
  )
}
<Tabs defaultSelectedKey="profile" orientation="vertical">
  <TabsList aria-label="User settings">
    <TabsTrigger id="profile">Profile</TabsTrigger>
    <TabsTrigger id="security">Security</TabsTrigger>
    <TabsTrigger id="notifications">Notifications</TabsTrigger>
  </TabsList>
  <TabPanel id="profile">Profile information</TabPanel>
  <TabPanel id="security">Security settings</TabPanel>
  <TabPanel id="notifications">Notification settings</TabPanel>
</Tabs>

Controlled

Overview of your dashboard with key metrics.

"use client"

import { useState } from "react"

import { Button } from "@/components/ui/button"
import {
  TabPanel,
  Tabs,
  TabsList,
  TabsTrigger,
} from "@/components/ui/tabs"

export function TabsControlled() {
  const [selectedTab, setSelectedTab] = useState("overview")

  return (
    <div className="space-y-4">
      <div className="flex gap-2">
        <Button
          variant="outline"
          size="sm"
          onPress={() => setSelectedTab("overview")}
        >
          Go to Overview
        </Button>
        <Button
          variant="outline"
          size="sm"
          onPress={() => setSelectedTab("analytics")}
        >
          Go to Analytics
        </Button>
      </div>
      <Tabs
        selectedKey={selectedTab}
        onSelectionChange={(key) => setSelectedTab(key as string)}
      >
        <TabsList aria-label="Dashboard sections">
          <TabsTrigger id="overview">Overview</TabsTrigger>
          <TabsTrigger id="analytics">Analytics</TabsTrigger>
          <TabsTrigger id="reports">Reports</TabsTrigger>
        </TabsList>
        <TabPanel id="overview">
          <p className="text-muted-foreground text-sm">
            Overview of your dashboard with key metrics.
          </p>
        </TabPanel>
        <TabPanel id="analytics">
          <p className="text-muted-foreground text-sm">
            Detailed analytics and insights.
          </p>
        </TabPanel>
        <TabPanel id="reports">
          <p className="text-muted-foreground text-sm">
            Generate and view reports.
          </p>
        </TabPanel>
      </Tabs>
    </div>
  )
}
"use client"
 
import { useState } from "react"
 
export default function TabsControlled() {
  const [selectedTab, setSelectedTab] = useState("overview")
 
  return (
    <Tabs
      selectedKey={selectedTab}
      onSelectionChange={(key) => setSelectedTab(key as string)}
    >
      <TabsList aria-label="Dashboard">
        <TabsTrigger id="overview">Overview</TabsTrigger>
        <TabsTrigger id="analytics">Analytics</TabsTrigger>
        <TabsTrigger id="reports">Reports</TabsTrigger>
      </TabsList>
      <TabPanel id="overview">Overview content</TabPanel>
      <TabPanel id="analytics">Analytics content</TabPanel>
      <TabPanel id="reports">Reports content</TabPanel>
    </Tabs>
  )
}

Using Menu Components Inside Tabs

When placing a Menu component as a sibling to TabsList (for example, in a toolbar), you need to use createHideableComponent to prevent React Aria's collection system from incorrectly treating the MenuItem components as part of the Tabs collection.

The Problem

React Aria's Tabs component uses a collection system to manage its children (TabTriggers). When you place a Menu inside the Tabs tree, the collection system can incorrectly process MenuItems as if they were TabTriggers, causing errors like:

TypeError: Cannot read properties of null (reading 'isDisabled')

See the related GitHub issue for more details.

The Solution

Wrap your Menu component using createHideableComponent from @react-aria/collections:

"use client"
 
import { useMemo } from "react"
import { createHideableComponent } from "@react-aria/collections"
import { MenuTrigger, Menu, MenuItem } from "@/components/ui/menu"
import { Button } from "@/components/ui/button"
import { Tabs, TabsList, TabsTrigger, TabPanel } from "@/components/ui/tabs"
 
export function TabsWithMenu() {
  // Create a hideable component to prevent Menu from being part of Tabs collection
  const SafeMenu = useMemo(
    () =>
      createHideableComponent(function () {
        return (
          <MenuTrigger>
            <Button variant="outline">Options</Button>
            <Menu>
              <MenuItem id="edit">Edit</MenuItem>
              <MenuItem id="delete">Delete</MenuItem>
            </Menu>
          </MenuTrigger>
        )
      }),
    []
  )
 
  return (
    <Tabs defaultSelectedKey="account">
      <div className="flex items-center justify-between">
        <TabsList aria-label="Settings">
          <TabsTrigger id="account">Account</TabsTrigger>
          <TabsTrigger id="security">Security</TabsTrigger>
        </TabsList>
        <SafeMenu />
      </div>
      <TabPanel id="account">Account settings</TabPanel>
      <TabPanel id="security">Security settings</TabPanel>
    </Tabs>
  )
}

Key Points

  • Wrap a function, not the component itself: createHideableComponent(function () { ... })
  • Include dependencies in useMemo if your Menu uses external state
  • This is only needed when Menu is a sibling to TabsList within the Tabs tree
  • The same pattern applies to other collection-based components like Select, ComboBox, etc.

API Reference

Tabs

The root tabs component that manages the selection state.

PropTypeDefaultDescription
defaultSelectedKeyKey-The initial selected tab (uncontrolled)
selectedKeyKey-The currently selected tab (controlled)
onSelectionChange(key: Key) => void-Handler called when tab selection changes
orientation'horizontal' | 'vertical''horizontal'The orientation of the tabs
isDisabledbooleanfalseWhether all tabs are disabled

TabsList

Container for the tab triggers.

PropTypeDefaultDescription
aria-labelstring-Accessible label for the tab list
childrenReactNode-The tab triggers to display

TabsTrigger

Individual tab trigger button.

PropTypeDefaultDescription
idKey-Unique identifier for the tab
isDisabledbooleanfalseWhether the tab is disabled
childrenReactNode-The content to display in the tab

TabPanel

The panel that displays the content for a selected tab.

PropTypeDefaultDescription
idKey-Must match the id of the corresponding trigger
childrenReactNode-The content to display when tab is selected

Accessibility

  • Uses the ARIA tabs pattern
  • Keyboard navigation with arrow keys
  • Tab key moves focus in and out of the tab list
  • Supports automatic and manual activation modes
  • Proper ARIA roles and attributes