---
title: "Tabs"
description: "Switch between sibling views within a single panel or screen."
links:
  doc: https://cladd.io/react/components/tabs/
  api: https://cladd.io/react/components/tabs/#api-reference
---

# 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`](/react/components/segmented/), so every accent, variant, and size that works there works here, and arrow-key navigation with roving focus is built in.

```tsx
<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

```tsx
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.

```tsx
<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`](/react/components/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.

```tsx
<Tabs defaultValue="general">
  <TabsList size={size}>
    <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`](/react/components/cladd-provider/) when unset, so you only reach for `activeColor` to override.

```tsx
<Tabs defaultValue="design">
  <TabsList activeColor={activeColor}>
    <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`](/react/components/input/) below, switch to Recipients, and switch back: the draft is still there.

```tsx
<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.

```tsx
<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`](/react/components/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.

```tsx
<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`](/react/components/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.

```tsx
<Tabs defaultValue="overview">
  <TabsList
    size={size}
    activeColor={activeColor}
    outline={outline}
    rounded={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

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children?` | `ReactNode` | — | The tab strip (`TabsList` with `Tab`s) and the `TabPanel`s. |
| `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`](/react/components/segmented/), so it also accepts every `Segmented` prop — `color`, `variant`, `activeColor`, `activeVariant`, `outline`, `size`, and `rounded`.

**Generics:** `C extends ElementType = 'div'`

**Inherits from:** [`Segmented`](/react/components/segmented/)

_No own props — see inherited types above._

### Tab

`Tab` extends [`SegmentedButton`](/react/components/segmented/), so it accepts that component's props in addition to the `value` below.

**Generics:** `C extends ElementType = 'button'`

**Inherits from:** [`SegmentedButton`](/react/components/segmented-button/)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | — | Identifies this tab. Matched against the `Tabs` selected value. |

### TabPanel

**Generics:** `C extends ElementType = 'div'`

| Prop | 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.<br>Preserves scroll position and internal state. Default `false`. |
| `value` | `string` | — | The tab `value` this panel belongs to. |
