Browse the docs

Tabs

Tabbed panels following the WAI-ARIA tabs pattern: arrow keys (and Home/End) move between tabs, selection follows focus, only the selected tab is in the tab order, and each panel is associated with its tab. The behavior lives in the tabs primitive; this component styles it and takes the panels as data.

This component needs the tabs primitive — two ways to get it:

Three projects, one of them archived. Last deploy 2 hours ago.
Three projects, one of them archived. Last deploy 2 hours ago.

Props

PropValuesDefaultDescription
itemsarrayrequiredOne object per tab: { value, label, content }.
defaultValuestringfirst itemWhich tab starts selected.
variant"line", "pill", "none""line"Underlined tabs, a segmented control, or undecorated — style "none" yourself via data-[state=active].
classNamestring""Appended to the outer wrapper.

Common patterns

<Tabs
  items={[
    { value: "overview", label: "Overview", content: <Overview /> },
    { value: "members", label: "Members", content: <Members /> },
  ]}
/>

// Start on a specific tab
<Tabs defaultValue="members" items={items} />

Notes

Panels are data, not composition. A Tabs/TabsList/TabsTrigger/TabsContent set would need React context to pass the selected value around, and context inside a copy-paste file is more machinery than this earns. Passing an array keeps the whole component in one file you can read at a glance.

The trade-off: content for every tab is constructed on render, even the hidden ones. If a panel is expensive, pass a component that fetches its own data rather than the data itself.

Only the selected panel is mounted, so state inside an inactive panel is lost when you switch away. Lift it out if you need it to survive.