v1.0

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.

Pro

Overview

Recent activity, open tasks, and where each project stands this week.

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.

The free build ships the horizontal-line layout with a sliding bottom indicator. Pro unlocks the full set of layout variants (vertical-button, vertical-line, horizontal-line, horizontal-button) and indicator positions (left, top, bottom).

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

Richer previews (the full set of layout variants and indicator positions) are marked with a Pro badge. The live demo still runs for everyone; the source needs a Pro seat.

Variants

The free build ships the horizontal-line layout. Pro unlocks vertical-button (a sliding pill), vertical-line (icon over label), and horizontal-button, so the tabs can match wherever they sit.

Key metrics and recent project activity at a glance.
Pro
Key metrics and recent project activity at a glance.
Pro
Key metrics and recent project activity at a glance.
Pro
Key metrics and recent project activity at a glance.

Indicators

The active tab is marked by a sliding line. The free build draws it on the bottom (shown above). Pro adds a top line and a vertical left rail for the button layouts.

Pro
Key metrics and recent project activity at a glance.
Pro
Key metrics and recent project activity at a glance.

Sizes

size on Tabs scales the triggers (sm, md default, and lg) and cascades to every TabsTrigger, so padding, type, and icon size scale together.

A compact bar for dense toolbars and side panels.
The default size for most in-page tab groups.
A roomy bar for top-level page sections.
Pro
A compact bar for dense side panels.
The default size for most tab groups.
A roomy bar for top-level page sections.

With Icon

Pass leading and/or trailing to a TabsTrigger. Icon size follows the active size, and the active tab's icon picks up the primary color.

Inbox

Messages waiting for your reply, newest first.

Pro
A timeline of everything that happened across your workspace.

Disabled

Set disabled on a TabsTrigger to dim it and skip it during arrow-key navigation.

Overview

Your key metrics and recent project activity at a glance.

Pro
Key metrics and recent project activity at a glance.

Controlled

Pass value and onValueChange to drive selection from your own state. Reach for it 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.
variant"vertical-button" "vertical-line" "horizontal-line" "horizontal-button""vertical-button"Pro: layout of the tab list. The free build only ships horizontal-line.
indicator"left" "top" "bottom"-Pro: where the sliding active line sits (left is for the vertical layouts). The free build always draws it on the bottom.
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.
leadingReact.ReactNode-Icon rendered before the label.
trailingReact.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.