Components
Tabs
View in FigmaAn underline tab bar — a token-styled wrapper over Base UI's Tabs. Give Tabs a defaultValue (or value/onValueChange to control it) and compose TabsList + TabsTab for the bar and TabsPanel for each panel. The selected tab is underlined by a bg-primary indicator that slides between tabs.
Install
npx shadcn@latest add @alix/tabsPreview
Team settings…
Usage
<Tabs defaultValue="team">
<TabsList>
<TabsTab value="team">Team</TabsTab>
<TabsTab value="billing">Billing</TabsTab>
</TabsList>
<TabsPanel value="team">Team settings…</TabsPanel>
<TabsPanel value="billing">Billing settings…</TabsPanel>
</Tabs>Examples
01
Equal-width tab row
Let each trigger flex when the tab set is a compact switcher with similarly weighted views.
Engagement summary for the current week.
<Tabs defaultValue="summary">
<TabsList className="w-full">
<TabsTab value="summary" className="flex-1">Summary</TabsTab>
<TabsTab value="people" className="flex-1">People</TabsTab>
<TabsTab value="finance" className="flex-1">Finance</TabsTab>
</TabsList>
<TabsPanel value="summary">Engagement summary for the current week.</TabsPanel>
<TabsPanel value="people">People allocation and senior coverage.</TabsPanel>
<TabsPanel value="finance">Revenue, margin, and working capital.</TabsPanel>
</Tabs>02
Controlled dashboard view
Control the selected value when adjacent UI needs to reflect the active tab.
Current viewpipeline
Six qualified opportunities are awaiting partner review.
const [view, setView] = useState("pipeline");
return (
<div className="w-full max-w-lg space-y-3">
<div className="flex items-center justify-between gap-4">
<span className="text-sm font-medium text-foreground">Current view</span>
<Badge variant="primary">{view}</Badge>
</div>
<Tabs value={view} onValueChange={setView}>
<TabsList>
<TabsTab value="pipeline">Pipeline</TabsTab>
<TabsTab value="delivery">Delivery</TabsTab>
<TabsTab value="risk">Risk</TabsTab>
</TabsList>
<TabsPanel value="pipeline">
<div className="border border-border p-4 text-sm text-muted-foreground">
Six qualified opportunities are awaiting partner review.
</div>
</TabsPanel>
<TabsPanel value="delivery">
<div className="border border-border p-4 text-sm text-muted-foreground">
Four active engagements need weekly steering notes.
</div>
</TabsPanel>
<TabsPanel value="risk">
<div className="border border-border p-4 text-sm text-muted-foreground">
Two workstreams are flagged for scope and timing risk.
</div>
</TabsPanel>
</Tabs>
</div>
);03
Panel content switcher
Tabs can switch between a populated DataTable and an EmptyState while Panel keeps one frame.
Engagement queue
Open client requests
| Request | Owner | Status |
|---|---|---|
| Liquidity review | A. Ward | In progress |
| Board pack refresh | M. Khan | Ready |
<Panel title="Engagement queue" description="Open client requests">
<Tabs defaultValue="active">
<TabsList>
<TabsTab value="active">Active</TabsTab>
<TabsTab value="blocked">Blocked</TabsTab>
</TabsList>
<TabsPanel value="active">
<DataTable
caption="Active engagement requests"
dense
rows={[
{ request: "Liquidity review", owner: "A. Ward", status: "In progress", tone: "primary" as const },
{ request: "Board pack refresh", owner: "M. Khan", status: "Ready", tone: "success" as const },
]}
columns={[
{ key: "request", header: "Request", cell: (row) => row.request, cellClassName: "font-medium" },
{ key: "owner", header: "Owner", cell: (row) => row.owner },
{ key: "status", header: "Status", cell: (row) => <Badge variant={row.tone}>{row.status}</Badge> },
]}
/>
</TabsPanel>
<TabsPanel value="blocked">
<EmptyState
title="No blocked requests"
description="Requests with missing inputs will appear here."
/>
</TabsPanel>
</Tabs>
</Panel>When to use it
Use it for
- Sibling views of the same object, such as engagement summary, people, finance, and risk.
- Compact dashboard panels where switching content avoids a second page or route.
- Controlled tab state when adjacent status, filters, or analytics need the selected value.
Reach for something else
- Primary application navigation across routes — use DashboardShell navigation instead.
- A short list of commands or links — use Menu instead.
- One finite form choice inside a field — use Select instead.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| Tabs | defaultValue?, value?, onValueChange? | — | The controller (Base UI's Tabs.Root). Set a default, or control it. |
| TabsList | children | — | The horizontal bar — renders the bottom border and the moving underline indicator. |
| TabsTab | value: string | — | A single tab trigger; its value pairs it with the TabsPanel of the same value. |
| TabsPanel | value: string | — | The content shown for the matching tab. Only the active panel is visible. |