# Tabs

Shows one panel of content at a time, chosen from a row of tabs.

- **Import:** `import { Tabs, TabsList, TabsTrigger, TabsContent } from "@booleanpress/ui/tabs"`
- **Radix Tabs:** <https://www.radix-ui.com/primitives/docs/components/tabs>
- **APG Tabs:** <https://www.w3.org/WAI/ARIA/apg/patterns/tabs/>
- **Page:** <https://ui.booleanpress.com/components/tabs> · @booleanpress/ui 0.1.0

## Usage

Tabs switch between views of the same thing, on one page. For a move to another page, use links or a navigation menu.

```tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@booleanpress/ui/tabs"

export function Mailer() {
  return (
    <Tabs defaultValue="overview">
      <TabsList>
        <TabsTrigger value="overview">Overview</TabsTrigger>
        <TabsTrigger value="logs">Logs</TabsTrigger>
      </TabsList>
      <TabsContent value="overview">Delivered today: 1,284</TabsContent>
      <TabsContent value="logs">The 50 most recent emails.</TabsContent>
    </Tabs>
  )
}
```

Each `TabsTrigger` and its `TabsContent` share a `value`. It is uncontrolled with `defaultValue`, or controlled with `value` and `onValueChange`. `TabsList` takes `variant`: `default` is a filled bar with a raised selected tab; `line` is a row with an underline. `orientation="vertical"` on `Tabs` stacks the triggers; give `Tabs` `flex-row` to put the panel beside them. Only the active panel is in the page.

## Examples

### Basic

Three tabs, the first selected, each with its own panel.

```tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@booleanpress/ui/tabs"

export default function TabsBasic() {
  return (
    <Tabs defaultValue="overview" className="w-full max-w-sm">
      <TabsList>
        <TabsTrigger value="overview">Overview</TabsTrigger>
        <TabsTrigger value="logs">Logs</TabsTrigger>
        <TabsTrigger value="settings">Settings</TabsTrigger>
      </TabsList>
      <TabsContent value="overview" className="text-sm">1,284 emails delivered today.</TabsContent>
      <TabsContent value="logs" className="text-sm">The 50 most recent emails.</TabsContent>
      <TabsContent value="settings" className="text-sm">Retry and retention settings.</TabsContent>
    </Tabs>
  )
}
```

### Line

`variant="line"` removes the fill and underlines the selected tab.

```tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@booleanpress/ui/tabs"

export default function TabsLine() {
  return (
    <Tabs defaultValue="open" className="w-full max-w-sm">
      <TabsList variant="line">
        <TabsTrigger value="open">Open</TabsTrigger>
        <TabsTrigger value="pending">Pending</TabsTrigger>
        <TabsTrigger value="closed">Closed</TabsTrigger>
      </TabsList>
      <TabsContent value="open" className="text-sm">12 open tickets.</TabsContent>
      <TabsContent value="pending" className="text-sm">4 tickets wait for a reply.</TabsContent>
      <TabsContent value="closed" className="text-sm">230 closed tickets.</TabsContent>
    </Tabs>
  )
}
```

### Vertical

`orientation="vertical"` stacks the tabs; Up and Down Arrow move between them.

```tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@booleanpress/ui/tabs"

export default function TabsVertical() {
  return (
    <Tabs defaultValue="general" orientation="vertical" className="w-full max-w-sm flex-row">
      <TabsList>
        <TabsTrigger value="general">General</TabsTrigger>
        <TabsTrigger value="mailers">Mailers</TabsTrigger>
        <TabsTrigger value="logging">Logging</TabsTrigger>
      </TabsList>
      <TabsContent value="general" className="text-sm">Sender name and address.</TabsContent>
      <TabsContent value="mailers" className="text-sm">The connections that send email.</TabsContent>
      <TabsContent value="logging" className="text-sm">What is kept, and for how long.</TabsContent>
    </Tabs>
  )
}
```

### Disabled

A disabled tab keeps its place, is skipped by the arrow keys and cannot be selected.

```tsx
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@booleanpress/ui/tabs"

export default function TabsDisabled() {
  return (
    <Tabs defaultValue="rules" className="w-full max-w-sm">
      <TabsList>
        <TabsTrigger value="rules">Rules</TabsTrigger>
        <TabsTrigger value="history" disabled>
          History
        </TabsTrigger>
      </TabsList>
      <TabsContent value="rules" className="text-sm">3 routing rules are active.</TabsContent>
      <TabsContent value="history" className="text-sm">Rule history.</TabsContent>
    </Tabs>
  )
}
```

## Accessibility

**Semantics.** A `tablist` of `tab` buttons (`aria-selected`, `aria-controls`) and a `tabpanel` named by its tab (`aria-labelledby`). `aria-orientation` is set on the list when vertical.

**Labels.** The text of each tab is its name. Name an icon-only tab with `aria-label`. If a page has more than one tab list, give each `TabsList` an `aria-label`.

**Focus.** The tab list is one tab stop: it is the selected tab, and the other tabs are reached with the arrow keys. Tab then moves into the active panel, which is itself focusable when it holds no focusable content.

**Known limits.**

- Selecting happens as focus moves (automatic activation). A panel that is slow to load should not be a tab: set `activationMode="manual"` on `Tabs` so the arrow keys move focus and Enter or Space selects.
- The selected tab is shown by fill, shadow and weight, and in the line variant by the underline: the underline is 2 px. The state is also in `aria-selected`.

### Keyboard

| Key | Behaviour |
| --- | --- |
| Tab | Moves focus into the tab list, onto the selected tab, then into the active panel. |
| Right Arrow | Moves to and selects the next tab, wrapping from the last to the first. |
| Left Arrow | Moves to and selects the previous tab, wrapping from the first to the last. |
| Down Arrow + Up Arrow | In a vertical list, moves to the next or previous tab. |
| Home | Moves to the first tab. |
| End | Moves to the last tab. |

## API

### Tabs

Renders Radix Tabs.Root and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `activationMode` | `"manual" \| "automatic"` |  | Whether a tab is activated automatically or manually. |
| `asChild` | `boolean` |  |  |
| `defaultValue` | `string` |  | The value of the tab to select by default, if uncontrolled |
| `dir` | `"ltr" \| "rtl"` |  | The direction of navigation between toolbar items. |
| `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) |
| `value` | `string` |  | The value for the selected tab, if controlled |

### TabsList

Renders Radix Tabs.List and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `loop` | `boolean` |  |  |

### TabsTrigger

Renders Radix Tabs.Trigger and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `value` | `string` |  | The value for the selected tab, if controlled |

### TabsContent

Renders Radix Tabs.Content and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `forceMount` | `true` |  | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. |
| `value` | `string` |  | The value for the selected tab, if controlled |

**Also exported:** `tabsListVariants`, the class names of TabsList's variants and sizes (`cva`), to give another element the same look.

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="tabs"` (Tabs), `data-slot="tabs-list"` (TabsList), `data-slot="tabs-trigger"` (TabsTrigger), `data-slot="tabs-content"` (TabsContent), and `data-orientation`, `data-variant`.

## Theming

The selected tab is `bg-card` (not the page colour, which is grey in BooleanSMTP and would be darker than the list). Inactive labels are `text-muted-foreground`.

| Token | Used for |
| --- | --- |
| `--card` | background |
| `--foreground` | text, background |
| `--input` | border, background |
| `--muted` | background |
| `--muted-foreground` | text |
| `--ring` | border, focus ring, outline |
