Overlays

Tabs

Switches between panels of related content.

Client component4 exports

Two visual treatments. The variant is read from context rather than taken as a prop on the list, so the root and the list can never disagree.

Import

import {
  Tabs,
  TabsList,
  TabsTrigger,
  TabsPanel,
} from "@abbainitiative/ui";

Also available from the subpath entry @abbainitiative/ui/tabs.

This component carries its own "use client" directive. You can still render it from a Server Component — you simply cannot pass it a function prop, because functions do not serialise across the boundary.

Examples

Line and enclosed

The line variant, which is the default.

The enclosed variant.

<Tabs defaultValue="overview">
  <TabsList aria-label="Project sections">
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsPanel value="overview">Overview content</TabsPanel>
  <TabsPanel value="activity">Activity content</TabsPanel>
  <TabsPanel value="settings">Settings content</TabsPanel>
</Tabs>

Props

Tabs props. All native attributes of the underlying element are also accepted and forwarded.
PropTypeDefaultDescription
variant"line" | "enclosed""line"Visual treatment of the tab list.
defaultValuestringInitially active tab when uncontrolled.
valuestringControlled active tab.
onValueChange(value: string) => voidCalled when the active tab changes.

Accessibility

  • Arrow keys move between tabs; Tab enters the list once and then moves to the panel (roving tabindex).
  • Each tab is wired to its panel with `aria-controls` and `aria-labelledby`.
  • Give TabsList an `aria-label` so the tab set is identifiable.
  • Many tabs scroll horizontally on narrow viewports rather than wrapping into a broken grid.