# Tabs

A set of panels in one view, with one panel in view at a time.

```tsx
import { Card, CardContent, Tabs, TabsContent, TabsList, TabsTrigger } from 'ferry-ui'

export default function TabsHero() {
  return (
    <Tabs defaultValue="overview" className="w-full max-w-lg">
      <TabsList aria-label="Project sections">
        <TabsTrigger value="overview">Overview</TabsTrigger>
        <TabsTrigger value="activity">Activity</TabsTrigger>
        <TabsTrigger value="members">Members</TabsTrigger>
      </TabsList>
      <TabsContent value="overview">
        <Card>
          <CardContent className="text-[13px] text-foreground-light">
            The project has 14 open tasks. The next release is on October 18.
          </CardContent>
        </Card>
      </TabsContent>
      <TabsContent value="activity">
        <Card>
          <CardContent className="text-[13px] text-foreground-light">
            Maya Chen moved the task "Pricing page" to the review column.
          </CardContent>
        </Card>
      </TabsContent>
      <TabsContent value="members">
        <Card>
          <CardContent className="text-[13px] text-foreground-light">
            The project has 6 members and 2 invitations with no answer.
          </CardContent>
        </Card>
      </TabsContent>
    </Tabs>
  )
}
```

## Usage guidelines

- **Panels of one view.** Tabs show one panel of a set at a time.
- **Not for routes.** For the pages of one area, use [Inner Menu](/docs/components/inner-menu) or links.
- **Not for filters.** To filter the same content or to change its format, use [Toggle Group](/docs/components/toggle-group).
- **Name the list.** If no visible heading names the tabs, give `TabsList` an `aria-label`.

## Anatomy

Import the parts and put them together. Give the same `value` to a `TabsTrigger` and to its `TabsContent`.

```tsx title="Anatomy"

<Tabs>
  <TabsList>
    <TabsTrigger />
  </TabsList>
  <TabsContent />
</Tabs>
```

| Part | Role |
| --- | --- |
| `Tabs` | Holds the value of the active tab. |
| `TabsList` | The row of triggers. It sets the look. |
| `TabsTrigger` | The button of one tab. |
| `TabsContent` | The panel of one tab. |

## Examples

### Variants

The `variant` prop of `TabsList` sets the look. The triggers follow the look of their list.

```tsx
import { Card, CardContent, CardHeader, CardTitle, Tabs, TabsContent, TabsList, TabsTrigger } from 'ferry-ui'

export default function TabsPills() {
  return (
    <Tabs defaultValue="monthly" className="w-full max-w-sm">
      <Card>
        <CardHeader>
          <CardTitle>Pro plan</CardTitle>
          <TabsList variant="pills" aria-label="Billing period">
            <TabsTrigger value="monthly">Monthly</TabsTrigger>
            <TabsTrigger value="yearly">Yearly</TabsTrigger>
          </TabsList>
        </CardHeader>
        <CardContent className="text-[13px] text-foreground-light">
          <TabsContent value="monthly">$24 for each seat, with one invoice each month.</TabsContent>
          <TabsContent value="yearly">$20 for each seat, with one invoice each year.</TabsContent>
        </CardContent>
      </Card>
    </Tabs>
  )
}
```

| Variant | Look | Use |
| --- | --- | --- |
| `underline` (default) | Text on a line that takes the full width. | The sections of a page. |
| `pills` | A compact control with a raised chip. | A card, a panel or a toolbar. |

### Icons and counts

A trigger can hold an icon and a count. Put the count in a [Badge](/docs/components/badge).

```tsx
import { Badge, Tabs, TabsContent, TabsList, TabsTrigger } from 'ferry-ui'
import { CircleCheck, CircleDot, GitPullRequest } from 'lucide-react'

export default function TabsIcons() {
  return (
    <Tabs defaultValue="open" className="w-full max-w-lg">
      <TabsList aria-label="Tasks">
        <TabsTrigger value="open">
          <CircleDot /> Open <Badge>24</Badge>
        </TabsTrigger>
        <TabsTrigger value="review">
          <GitPullRequest /> In review <Badge>3</Badge>
        </TabsTrigger>
        <TabsTrigger value="closed">
          <CircleCheck /> Closed
        </TabsTrigger>
      </TabsList>
      <TabsContent value="open" className="text-[13px] text-foreground-light">
        The 24 tasks that are open.
      </TabsContent>
      <TabsContent value="review" className="text-[13px] text-foreground-light">
        The 3 tasks that wait for a review.
      </TabsContent>
      <TabsContent value="closed" className="text-[13px] text-foreground-light">
        The tasks that are done.
      </TabsContent>
    </Tabs>
  )
}
```

### Disabled tab

Set `disabled` on the trigger of a tab that is not available.

```tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from 'ferry-ui'

export default function TabsDisabled() {
  return (
    <Tabs defaultValue="overview" className="w-full max-w-lg">
      <TabsList aria-label="Project sections">
        <TabsTrigger value="overview">Overview</TabsTrigger>
        <TabsTrigger value="invoices">Invoices</TabsTrigger>
        <TabsTrigger value="audit" disabled>
          Audit log
        </TabsTrigger>
      </TabsList>
      <TabsContent value="overview" className="text-[13px] text-foreground-light">
        The overview of the project.
      </TabsContent>
      <TabsContent value="invoices" className="text-[13px] text-foreground-light">
        The invoices of the project.
      </TabsContent>
    </Tabs>
  )
}
```

### Controlled value

`Tabs` holds its value by default. Use `defaultValue` to set the first active tab.

To control the value, pass `value` and `onValueChange`. Use this to change the tab from another control.

```tsx
import * as React from 'react'
import { Button, Tabs, TabsContent, TabsList, TabsTrigger } from 'ferry-ui'

const STEPS = ['details', 'members', 'review']

export default function TabsControlled() {
  const [step, setStep] = React.useState('details')
  const index = STEPS.indexOf(step)

  return (
    <Tabs value={step} onValueChange={setStep} className="w-full max-w-md">
      <TabsList variant="pills" aria-label="New project">
        <TabsTrigger value="details">Details</TabsTrigger>
        <TabsTrigger value="members">Members</TabsTrigger>
        <TabsTrigger value="review">Review</TabsTrigger>
      </TabsList>
      <TabsContent value="details" className="text-[13px] text-foreground-light">
        The name and the description of the project.
      </TabsContent>
      <TabsContent value="members" className="text-[13px] text-foreground-light">
        The members who get an invitation.
      </TabsContent>
      <TabsContent value="review" className="text-[13px] text-foreground-light">
        A summary of the project before you create it.
      </TabsContent>
      <div className="flex justify-between">
        <Button size="tiny" disabled={index === 0} onClick={() => setStep(STEPS[index - 1] ?? step)}>
          Back
        </Button>
        <Button size="tiny" disabled={index === STEPS.length - 1} onClick={() => setStep(STEPS[index + 1] ?? step)}>
          Next
        </Button>
      </div>
    </Tabs>
  )
}
```

### State of a panel

A panel unmounts when its tab is not active. Its fields lose their content.

To keep a panel mounted, pass `forceMount` to `TabsContent`. Then add the class `data-[state=inactive]:hidden` to hide the panel that is not active.

```tsx
import { Field, Input, Tabs, TabsContent, TabsList, TabsTrigger, Textarea } from 'ferry-ui'

export default function TabsForceMount() {
  return (
    <Tabs defaultValue="details" className="w-full max-w-md">
      <TabsList aria-label="Invoice form">
        <TabsTrigger value="details">Details</TabsTrigger>
        <TabsTrigger value="notes">Notes</TabsTrigger>
      </TabsList>
      {/* Each panel stays mounted. The class hides the panel that is not active. */}
      <TabsContent value="details" forceMount className="data-[state=inactive]:hidden">
        <Field label="Customer">
          <Input placeholder="Acme" />
        </Field>
      </TabsContent>
      <TabsContent value="notes" forceMount className="data-[state=inactive]:hidden">
        <Field label="Notes for the customer">
          <Textarea placeholder="Thank you for your order." />
        </Field>
      </TabsContent>
    </Tabs>
  )
}
```

### Many tabs

If the triggers are wider than the `underline` list, the list scrolls horizontally. It shows no scrollbar.

```tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from 'ferry-ui'

// A value has no space: the tab and its panel use it in their `id`.
const SECTIONS = [
  { value: 'overview', label: 'Overview' },
  { value: 'profile', label: 'Profile' },
  { value: 'notifications', label: 'Notifications' },
  { value: 'security', label: 'Security' },
  { value: 'billing', label: 'Billing' },
  { value: 'members', label: 'Members' },
  { value: 'api-keys', label: 'API keys' },
  { value: 'audit-log', label: 'Audit log' },
]

export default function TabsOverflow() {
  return (
    <Tabs defaultValue="overview" className="w-full max-w-xs">
      <TabsList aria-label="Account settings">
        {SECTIONS.map((section) => (
          <TabsTrigger key={section.value} value={section.value}>
            {section.label}
          </TabsTrigger>
        ))}
      </TabsList>
      {SECTIONS.map((section) => (
        <TabsContent key={section.value} value={section.value} className="text-[13px] text-foreground-light">
          The panel of the tab "{section.label}".
        </TabsContent>
      ))}
    </Tabs>
  )
}
```

## Accessibility

- The arrow keys move the focus to the next tab and activate it.
- With `activationMode="manual"`, the arrow keys only move the focus. <Kbd>Enter</Kbd> or <Kbd>Space</Kbd> activates the tab.
- <Kbd>Tab</Kbd> moves the focus from the active trigger to its panel.

## API reference

Each part also accepts the props of its Radix UI primitive and the attributes of its element.

### Tabs

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` |  | The value for the selected tab, if controlled |
| `defaultValue` | `string` |  | The value of the tab to select by default, if uncontrolled |
| `onValueChange` | `((value: string) => void)` |  | A function called when a new tab is selected |
| `orientation` | `"horizontal" \| "vertical"` | `horizontal` | The orientation the tabs are layed out. Mainly so arrow navigation is done accordingly (left & right vs. up & down) |
| `dir` | `"ltr" \| "rtl"` |  | The direction of navigation between toolbar items. |
| `activationMode` | `"manual" \| "automatic"` | `automatic` | Whether a tab is activated automatically or manually. |
| `asChild` | `boolean` |  |  |

### TabsList

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"underline" \| "pills"` | `underline` | `underline` = page tabs on a bottom border (default); `pills` = compact segmented control. |
| `loop` | `boolean` |  |  |
| `asChild` | `boolean` |  |  |

### TabsTrigger

`value` is required.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` (required) | `string` |  |  |
| `asChild` | `boolean` |  |  |

### TabsContent

`value` is required.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` (required) | `string` |  |  |
| `forceMount` | `true` |  | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. |
| `asChild` | `boolean` |  |  |

### tabsListVariants

`tabsListVariants({ variant })` returns the classes of `TabsList`. Use it to give the look of the list to another element.

| Option | Type | Role |
| --- | --- | --- |
| `variant` | `'underline'` or `'pills'` | The look of the list. The default is `underline`. |
