Tabs
Tabs partitions one region of the screen into a set of sibling views — Overview / Activity / Settings, Write / Preview / Diff, the panels of an inspector — and shows one at a time. It's four pieces that compose: a stateful Tabs root that owns the selected value, a TabsList strip of Tab buttons, and a TabPanel for each value. The strip is rendered with Segmented, so every accent, variant, and size that works there works here, and arrow-key navigation with roving focus is built in.
<Tabs defaultValue="overview">
<TabsList>
<Tab value="overview">Overview</Tab>
<Tab value="activity">Activity</Tab>
<Tab value="settings">Settings</Tab>
</TabsList>
<TabPanel value="overview" className="pt-4 text-cladd-fg-soft">
A summary of the project — status, recent changes, and the people
working on it.
</TabPanel>
<TabPanel value="activity" className="pt-4 text-cladd-fg-soft">
A reverse-chronological feed of commits, comments, and deploys.
</TabPanel>
<TabPanel value="settings" className="pt-4 text-cladd-fg-soft">
Visibility, integrations, and danger-zone controls for this project.
</TabPanel>
</Tabs>Usage
import { Tabs, TabsList, Tab, TabPanel } from '@cladd-ui/react';
<Tabs defaultValue="overview">
<TabsList>
<Tab value="overview">Overview</Tab>
<Tab value="activity">Activity</Tab>
</TabsList>
<TabPanel value="overview">…</TabPanel>
<TabPanel value="activity">…</TabPanel>
</Tabs>;Tabs renders no DOM of its own — it's the state container. Each Tab and its TabPanel are paired by a matching value; clicking a tab (or arrowing to it) shows the panel with the same value and unmounts the rest. Use defaultValue for the uncontrolled case and value + onValueChange when you need to drive the selection from outside.
Examples
Controlled
Pass value and onValueChange to own the selection yourself — handy when the active tab is part of app state, synced to the URL, or set by something other than a click. Here the current value is mirrored below the panels.
Active tab: write
<Tabs value={value} onValueChange={setValue}>
<TabsList>
<Tab value="write">Write</Tab>
<Tab value="preview">Preview</Tab>
<Tab value="diff">Diff</Tab>
</TabsList>
<TabPanel value="write" className="pt-4 text-cladd-fg-soft">
The raw editor — type your changes here.
</TabPanel>
<TabPanel value="preview" className="pt-4 text-cladd-fg-soft">
A rendered preview of what you wrote.
</TabPanel>
<TabPanel value="diff" className="pt-4 text-cladd-fg-soft">
Line-by-line changes against the saved version.
</TabPanel>
<p className="pt-2 text-cladd-fg-softer">
Active tab: <span className="text-cladd-primary">{value}</span>
</p>
</Tabs>Sizes
TabsList carries the Segmented size prop across the full 2xs → 2xl scale; md is the default and the right call for most app chrome. The whole strip and its tabs scale together, so the panels stay anchored to the same rhythm as the rest of your UI.
<Tabs defaultValue="general">
<TabsList size="md">
<Tab value="general">General</Tab>
<Tab value="members">Members</Tab>
<Tab value="billing">Billing</Tab>
</TabsList>
<TabPanel value="general" className="pt-4 text-cladd-fg-soft">
Workspace name, slug, and default locale.
</TabPanel>
<TabPanel value="members" className="pt-4 text-cladd-fg-soft">
Invite teammates and manage their roles.
</TabPanel>
<TabPanel value="billing" className="pt-4 text-cladd-fg-soft">
Plan, payment method, and invoices.
</TabPanel>
</Tabs>Colors
activeColor tints the selected tab with any of the eleven cladd accents, while the rest of the strip stays neutral — the accent reads as "you are here". Like the other accent-aware controls it inherits the theme accent from CladdProvider when unset, so you only reach for activeColor to override.
<Tabs defaultValue="design">
<TabsList activeColor="blue">
<Tab value="design">Design</Tab>
<Tab value="prototype">Prototype</Tab>
<Tab value="inspect">Inspect</Tab>
</TabsList>
<TabPanel value="design" className="pt-4 text-cladd-fg-soft">
The canvas — drag, draw, and arrange layers.
</TabPanel>
<TabPanel value="prototype" className="pt-4 text-cladd-fg-soft">
Wire up flows and transitions between frames.
</TabPanel>
<TabPanel value="inspect" className="pt-4 text-cladd-fg-soft">
Measurements, tokens, and exportable code.
</TabPanel>
</Tabs>Keep panels mounted
By default an inactive TabPanel unmounts, so its state resets when you leave. Pass keepMounted to keep it in the tree (hidden) instead — the canonical case is a form field whose typed value should survive a trip to another tab. Type into the Input below, switch to Recipients, and switch back: the draft is still there.
<Tabs defaultValue="compose">
<TabsList>
<Tab value="compose">Compose</Tab>
<Tab value="recipients">Recipients</Tab>
</TabsList>
<TabPanel value="compose" keepMounted className="pt-4">
<Input
placeholder="Type a draft, switch tabs, come back…"
value={draft}
onChange={(value) => setDraft(value)}
className="w-full"
/>
</TabPanel>
<TabPanel value="recipients" className="pt-4 text-cladd-fg-soft">
Pick who this goes to. Your draft is still in the Compose tab.
</TabPanel>
</Tabs>Disabled tab
A Tab accepts disabled to dim it and take it out of both the click target and the arrow-key rotation — use it for a view gated behind a plan tier or a permission the current user lacks.
<Tabs defaultValue="account">
<TabsList>
<Tab value="account">Account</Tab>
<Tab value="team">Team</Tab>
<Tab value="audit" disabled>
Audit log
</Tab>
</TabsList>
<TabPanel value="account" className="pt-4 text-cladd-fg-soft">
Your profile, email, and password.
</TabPanel>
<TabPanel value="team" className="pt-4 text-cladd-fg-soft">
Members and pending invitations.
</TabPanel>
<TabPanel value="audit" className="pt-4 text-cladd-fg-soft">
Available on the Enterprise plan.
</TabPanel>
</Tabs>In a toolbar
Tabs earn their keep as an app-section switcher. Drop a TabsList straight into a Toolbar — a transparent variant lets it sit flush in the bar — and you get a dense header that switches the main content region. This is the layout cladd was built for: a lot on screen, still legible.
<Surface outline className="w-full max-w-xl rounded-2xl">
<Tabs defaultValue="canvas">
<Toolbar className="w-full" contentClassName="justify-between">
<span className="px-2 text-cladd-fg-soft">Untitled board</span>
<TabsList variant="transparent">
<Tab value="canvas">Canvas</Tab>
<Tab value="data">Data</Tab>
<Tab value="history">History</Tab>
</TabsList>
</Toolbar>
<div className="p-4">
<TabPanel value="canvas" className="text-cladd-fg-soft">
The infinite canvas — nodes, edges, and the toolbox.
</TabPanel>
<TabPanel value="data" className="text-cladd-fg-soft">
The underlying table powering every node.
</TabPanel>
<TabPanel value="history" className="text-cladd-fg-soft">
Snapshots and named versions you can roll back to.
</TabPanel>
</div>
</Tabs>
</Surface>Playground
size, activeColor, and the Segmented outline / rounded toggles all compose on TabsList. Default to md; reach for lg when the tab strip is the primary navigation of a screen.
<Tabs defaultValue="overview">
<TabsList
size="md"
activeColor="blue"
outline
rounded
>
<Tab value="overview">Overview</Tab>
<Tab value="activity">Activity</Tab>
<Tab value="settings">Settings</Tab>
</TabsList>
<TabPanel value="overview" className="pt-4 text-cladd-fg-soft">
A summary of the project and the people working on it.
</TabPanel>
<TabPanel value="activity" className="pt-4 text-cladd-fg-soft">
A feed of commits, comments, and deploys.
</TabPanel>
<TabPanel value="settings" className="pt-4 text-cladd-fg-soft">
Visibility, integrations, and danger-zone controls.
</TabPanel>
</Tabs>API Reference
Tabs
| Name: Type | Default | Description |
|---|---|---|
| children: ReactNode | — | The tab strip (TabsList with Tabs) and the TabPanels. |
| defaultValue: string | — | Initially selected tab value (uncontrolled). Ignored when value is provided. |
| onValueChange: (value: string) => void | — | Fires whenever the selected tab changes (click or keyboard). |
| value: string | — | Controlled selected tab value. When provided, internal state is bypassed. |
TabsList
TabsList renders a Segmented, so it also accepts every Segmented prop — color, variant, activeColor, activeVariant, outline, size, and rounded.
| Name: Type | Default | Description |
|---|---|---|
| No own props — see inherited types above. | ||
Tab
Tab extends SegmentedButton, so it accepts that component's props in addition to the value below.
| Name: Type | Default | Description |
|---|---|---|
| value*: string | — | Identifies this tab. Matched against the Tabs selected value. |
TabPanel
| Name: Type | Default | Description |
|---|---|---|
| as: ElementType | 'div' | Polymorphic root element. Defaults to 'div'. |
| children: ReactNode | — | Panel content, shown when this panel's value is the selected tab. |
| className: string | — | Extra classes for the panel root. |
| keepMounted: boolean | false | Keep the panel in the DOM (just hidden) while inactive instead of unmounting it.Preserves scroll position and internal state. Default false. |
| value*: string | — | The tab value this panel belongs to. |