Tabs

Tabbed panels following the WAI-ARIA tabs pattern: arrow keys move between tabs, only the selected tab is in the tab order, and each panel is associated with its tab.

This is the first component that holds state, so it imports useState from React and carries a "use client" directive.

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""line"Underlined tabs, or a segmented control.
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.