v0.5

Tabs

A set of layered content panels — one shown at a time — switched by an accessible tab list.

Overview

Recent activity, open tasks, and where each project stands this week, all in one place.

Description

Tabs is a compound component (Tabs + TabsList + TabsTrigger + TabsContent) built on the Radix Tabs primitive. The list is a real role="tablist" of role="tab" buttons; each TabsContent is a role="tabpanel" linked to its trigger, and only the active panel is shown. Selecting a trigger swaps the visible panel in place.

Reach for it when each option reveals a content panel within the same surface (settings forms, a detail card with sub-views, an editor with modes). The triggers use the same line styling as TabMenu: a primary underline that slides to the active tab.

If your options instead navigate to routes or sections elsewhere on the page (no in-place panel), use TabMenu — it renders the tab list only and supports asChild links. For 2 to 5 inline, mutually exclusive options without rich item content, use SegmentedControl.

Installation

pnpm dlx @create-ui/cli add tabs

Anatomy

Tabs owns the selected value and the size context. TabsList wraps the triggers; each TabsTrigger is paired by value to a TabsContent panel.

<Tabs defaultValue="overview">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
  </TabsList>
  <TabsContent value="overview">...</TabsContent>
  <TabsContent value="activity">...</TabsContent>
</Tabs>

Usage

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
<Tabs defaultValue="overview">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
  </TabsList>
  <TabsContent value="overview">Overview content</TabsContent>
  <TabsContent value="activity">Activity content</TabsContent>
</Tabs>

Examples

Controlled

Pass value and onValueChange to drive selection from your own state — useful when other UI needs to react to the active tab, or to persist it to the URL. Omit them (use defaultValue) for a simple uncontrolled switcher.

Your key metrics and recent project activity at a glance.

Active: overview

Accessibility

Built on the Radix Tabs primitive, so it follows the WAI-ARIA Tabs pattern: the list is role="tablist", triggers are role="tab" with aria-selected, and each panel is role="tabpanel" wired to its trigger via aria-controls / aria-labelledby. Focus uses a roving tabindex — one tab stop for the whole list, arrow keys move between tabs.

KeyDescription
TabMoves focus into the list (active tab) and out to the panel.
Moves between tabs.
Home EndMoves to the first / last tab.
Space EnterActivates the focused tab.

ARIA notes:

  • Activation is automatic by default (focusing a tab selects it). Pass activationMode="manual" on Tabs to require Enter/Space.
  • Disabled triggers are skipped by arrow-key navigation.
  • If a trigger only shows an icon, add an aria-label so it announces its purpose.

Styling

Tailwind override: pass className to merge Tailwind classes with the component's CVA classes (via cn()).

<TabsList className="w-full">
  <TabsTrigger value="overview" className="flex-1">
    Overview
  </TabsTrigger>
</TabsList>

Data slots and attributes: the component sets these for CSS targeting.

  • data-slot="tabs" on the root, data-slot="tabs-list" on the list, data-slot="tabs-trigger" on each trigger, data-slot="tabs-content" on each panel.
  • data-slot="tabs-active-line" on the sliding active indicator; data-slot="tabs-trigger-label" on the label; data-slot="tabs-trigger-icon" (with data-position="leading" | "trailing") on each icon.
  • data-size="<size>" on the root and each trigger.
  • Radix sets data-state="active" | "inactive" on the relevant elements.

Target the active trigger in CSS:

[data-slot="tabs-trigger"][data-state="active"] {
  /* ... */
}
  • TabMenu: use this when the options navigate (routes or in-page sections) instead of swapping a panel in place; supports asChild links.
  • SegmentedControl: use this for 2 to 5 inline mutually exclusive options without rich item content.
  • Accordion: use this to expand multiple stacked regions at once rather than one panel at a time.

API Reference

Tabs

Root that owns selection state and the size context. Wraps Radix Tabs.Root, so its props (dir, activationMode, etc.) are accepted.

Props

PropTypeDefaultDescription
defaultValuestring-Uncontrolled initial active tab.
valuestring-Controlled active tab.
onValueChange(value: string) => void-Fires when the active tab changes.
size"sm" "md" "lg""md"Size scale handed to triggers via context.
classNamestring-Classes merged with the component's CVA classes.

TabsList

Wraps Radix Tabs.List. Renders role="tablist" and the baseline border.

TabsTrigger

Wraps Radix Tabs.Trigger. Renders role="tab".

Props

PropTypeDefaultDescription
valuestring-Pairs the trigger to the TabsContent with the same value.
disabledboolean-Disables the tab; skipped by arrow-key navigation.
leadingIconReact.ReactNode-Icon rendered before the label.
trailingIconReact.ReactNode-Icon rendered after the label.
childrenReact.ReactNode-The tab label.
classNamestring-Classes merged with the component's CVA classes.

TabsContent

Wraps Radix Tabs.Content. Renders role="tabpanel"; only the panel whose value matches the active tab is shown.

Props

PropTypeDefaultDescription
valuestring-Pairs the panel to the TabsTrigger with the same value.
classNamestring-Classes merged with the component's CVA classes.