Skip to content
View as Markdown

Tabs ​

The Tabs component provides tab-based navigation for toggling between related panels on the same page. It is backed by Base UI's Tabs primitive.

Live preview in the UI Catalogue →

Import ​

tsx
import { Tabs } from "@tailor-platform/app-shell";

Basic Usage ​

tsx
<Tabs.Root defaultValue="overview">
  <Tabs.List>
    <Tabs.Tab value="overview">Overview</Tabs.Tab>
    <Tabs.Tab value="projects">Projects</Tabs.Tab>
    <Tabs.Tab value="account">Account</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel value="overview">Overview content</Tabs.Panel>
  <Tabs.Panel value="projects">Projects content</Tabs.Panel>
  <Tabs.Panel value="account">Account content</Tabs.Panel>
</Tabs.Root>

Sub-components ​

Sub-componentDescription
Tabs.RootManages tab selection state
Tabs.ListGroups the individual tab buttons
Tabs.TabAn interactive tab button that toggles the corresponding panel
Tabs.PanelA panel displayed when the corresponding tab is active

Props ​

Tabs.Root Props ​

PropTypeDefaultDescription
defaultValueTabs.Tab.Value0Initial active tab value (uncontrolled)
valueTabs.Tab.Value-Controlled active tab value
onValueChange(value: any) => void-Callback when the active tab changes
variant'default' | 'line' | 'capsule''default'Visual style of the tabs
size'xs' | 'sm' | 'default' | 'lg''default'Minimum size (capsule only) — mirrors Button's height tiers
classNamestring-Additional CSS classes for root
childrenReact.ReactNode-Tabs sub-components

Tabs.List Props ​

Accepts className and all standard HTML <div> props.

Tabs.Tab Props ​

PropTypeDefaultDescription
valueTabs.Tab.Value-Required. The value of the tab
disabledboolean-Whether the tab is disabled

Also accepts className and all standard HTML <button> props.

Tabs.Panel Props ​

PropTypeDefaultDescription
valueTabs.Tab.Value-Required. The value matching the corresponding tab
keepMountedbooleanfalseWhether to keep the panel in the DOM when hidden

Also accepts className and all standard HTML <div> props.

Controlled Usage ​

tsx
const [activeTab, setActiveTab] = useState("overview");

<Tabs.Root value={activeTab} onValueChange={setActiveTab}>
  <Tabs.List>
    <Tabs.Tab value="overview">Overview</Tabs.Tab>
    <Tabs.Tab value="details">Details</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel value="overview">Overview content</Tabs.Panel>
  <Tabs.Panel value="details">Details content</Tabs.Panel>
</Tabs.Root>;

Examples ​

With Disabled Tab ​

tsx
<Tabs.Root defaultValue="active">
  <Tabs.List>
    <Tabs.Tab value="active">Active</Tabs.Tab>
    <Tabs.Tab value="pending">Pending</Tabs.Tab>
    <Tabs.Tab value="archived" disabled>
      Archived
    </Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel value="active">Active items</Tabs.Panel>
  <Tabs.Panel value="pending">Pending items</Tabs.Panel>
  <Tabs.Panel value="archived">Archived items</Tabs.Panel>
</Tabs.Root>

Use Icons in Tab ​

In the capsule variant, a tab whose only child is an icon renders square — its min-width matches its min-height — making it a natural fit for icon-only view toggles (e.g. table/kanban). Use size to align the track with a sibling Button: size="default" (36px) matches Button's default height. Unsized icons default to 16px, so the square is consistent regardless of the icon.

Accessibility: an icon-only tab has no visible text, so give each one an aria-label. Without it, screen readers announce an unnamed tab.

tsx
<Tabs.Root defaultValue="overview" variant="capsule" size="default">
  <Tabs.List>
    <Tabs.Tab value="overview" aria-label="Overview">
      <LayoutDashboardIcon />
    </Tabs.Tab>
    <Tabs.Tab value="projects" aria-label="Projects">
      <FolderKanbanIcon />
    </Tabs.Tab>
    <Tabs.Tab value="settings" aria-label="Settings">
      <SettingsIcon />
    </Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel value="overview">Overview content</Tabs.Panel>
  <Tabs.Panel value="projects">Projects content</Tabs.Panel>
  <Tabs.Panel value="settings">Settings content</Tabs.Panel>
</Tabs.Root>

Sizing the capsule variant ​

size mirrors Button's height tiers, so a capsule can sit flush next to a button. The value sets a minimum height — taller content still grows.

sizeTrack heightMatches Button
xs28pxsize="xs"
sm32pxsize="sm"
default36pxdefault
lg40pxsize="lg"