Tabs

Switch between panels in the same context. Full WAI-ARIA keyboard support: arrows rove, Home and End jump, and only the active tab is in the tab order.

src/components/ui/tabs.tsx

Install

npx astralyx-ui add tabs

Usage

import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'

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

Composer

Change the props and the source below follows.

Everything at a glance.
Props
tsx
<Tabs defaultValue="overview">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="overview">…</TabsContent>
</Tabs>

Examples

A tab group

Browser

Solid

Underline

Vertical

Disabled trigger

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

Tabs

Extends Omit<ComponentProps<'div'>, 'onChange'>.

PropTypeDefaultDescription
defaultValuestring | number | readonly string[]—Controlled and uncontrolled selection, on Tabs.
variantTabsVariant'solid'Set it once on Tabs and the list, the triggers and the panel all follow. Setting it on a child still wins, which is how it was written before the root took the prop.
sizeSegmentSize'default'The Button's size scale. A solid track takes the Button's height and corner, so a tab list in a toolbar lines up with the Buttons and Inputs of the same size; every variant takes its label and icon size. Set on Tabs or TabsList and the triggers inherit it; a size on a trigger still wins.
valuestring—Controlled and uncontrolled selection, on Tabs.
onValueChange(value: string) => void—Fires with the newly selected tab.
orientation'horizontal' | 'vertical''horizontal'Layout direction, and which arrow keys move between tabs.
activationMode'automatic' | 'manual''automatic'Automatic selects as focus moves; manual waits for Enter or Space.

TabsList

Extends ComponentProps<'div'>.

PropTypeDefaultDescription
variant'solid' | 'underline' | 'browser''solid'Set it once on Tabs and the list, the triggers and the panel all follow. Setting it on a child still wins, which is how it was written before the root took the prop.
size'sm' | 'default' | 'xs' | 'lg' | 'xl''default'The Button's size scale. A solid track takes the Button's height and corner, so a tab list in a toolbar lines up with the Buttons and Inputs of the same size; every variant takes its label and icon size. Set on Tabs or TabsList and the triggers inherit it; a size on a trigger still wins.
orientation'horizontal' | 'vertical''horizontal'Layout direction, and which arrow keys move between tabs.

TabsTrigger

Extends Omit<ComponentProps<'button'>, 'value'>.

PropTypeDefaultDescription
variant'solid' | 'underline' | 'browser''solid'Set it once on Tabs and the list, the triggers and the panel all follow. Setting it on a child still wins, which is how it was written before the root took the prop.
size'sm' | 'default' | 'xs' | 'lg' | 'xl''default'The Button's size scale. A solid track takes the Button's height and corner, so a tab list in a toolbar lines up with the Buttons and Inputs of the same size; every variant takes its label and icon size. Set on Tabs or TabsList and the triggers inherit it; a size on a trigger still wins.
valuestringrequiredRequired. Matches the TabsContent it controls.

TabsContent

A panel.

Extends ComponentProps<'div'>.

PropTypeDefaultDescription
valuestringrequiredControlled and uncontrolled selection, on Tabs.
keepMountedbooleanfalse—
variantTabsVariant'solid'Set it once on Tabs and the list, the triggers and the panel all follow. Setting it on a child still wins, which is how it was written before the root took the prop.

Functions

FunctionSignatureDescription
listVariants(props?: { variant?: 'solid' | 'underline' | 'browser'; orientation?: 'horizontal' | 'vertical'; size?: 'sm' | 'default' | 'xs' | 'lg' | 'xl' }) => string—
triggerVariants(props?: { variant?: 'solid' | 'underline' | 'browser'; size?: 'sm' | 'default' | 'xs' | 'lg' | 'xl' }) => string—

Behaviour

TopicDetailDescription
browser—The connected look: the active tab and the panel are one fill with nothing drawn between them, and two masked flares carve the strip away at the bottom corners. No borders anywhere — a bordered version has to hide one border with another and comes apart at every zoom level.
transition—The panel cross-fades on switch and does not move. Travel on a panel root means a transform, and a transform is a containing block for any fixed layer inside it — a popover opened from a tab would land in the wrong place. The tab shape morphs instead, flares included.