---
id: "packages/active-ui/tabs"
title: "Tabs"
description: "Views of the same subject shown one at a time, with a row or column of tabs to switch."
url: "/packages/active-ui/tabs"
locale: "en"
revision: "0bcf7248a86020aa"
---

# Tabs

Tabs show one of several views of the same subject: the overview, the participants and the
documents of a scheme. The reader switches without leaving the page. Use them for parallel content;
for steps in a sequence, use a stepper, and for pages with their own address, links. The composed
[`ResponsiveTabs`](/packages/active-ui/responsive-tabs) turns tabs into a select on a narrow screen.

**Example: Default, line and vertical**

Client: Runs entirely in your browser. Source: `examples/ui/tabs.tsx`.

```tsx
// apps/docs/examples/ui/tabs.tsx
'use client';

import {
  Tabs,
  TabsContent,
  TabsList,
  TabsTrigger,
} from '@workspace/active-ui/components/primitives/tabs';

const PANELS = [
  [
    'overview',
    'Overview',
    'The scheme has 214 participants and runs since 2019.',
  ],
  [
    'participants',
    'Participants',
    '198 active, 12 deferred and 4 retired members.',
  ],
  [
    'documents',
    'Documents',
    'Regulations, the last statement and two notices.',
  ],
] as const;

/**
 * Views of the same subject, one at a time: the default and the `line`
 * variant, and a vertical list. The arrow keys move between the tabs, and
 * Enter or Space shows the panel of the focused tab.
 */
export default function TabsExample() {
  return (
    <div className="grid w-full max-w-lg gap-10">
      <Tabs defaultValue="overview">
        <TabsList aria-label="Scheme">
          {PANELS.map(([value, label]) => (
            <TabsTrigger key={value} value={value}>
              {label}
            </TabsTrigger>
          ))}
        </TabsList>
        {PANELS.map(([value, , text]) => (
          <TabsContent key={value} value={value} className="pt-3">
            {text}
          </TabsContent>
        ))}
      </Tabs>

      <Tabs defaultValue="participants">
        <TabsList variant="line" aria-label="Scheme, line tabs">
          {PANELS.map(([value, label]) => (
            <TabsTrigger key={value} value={value}>
              {label}
            </TabsTrigger>
          ))}
        </TabsList>
        {PANELS.map(([value, , text]) => (
          <TabsContent key={value} value={value} className="pt-3">
            {text}
          </TabsContent>
        ))}
      </Tabs>

      <Tabs defaultValue="documents" orientation="vertical">
        <TabsList aria-label="Scheme, vertical tabs">
          {PANELS.map(([value, label]) => (
            <TabsTrigger key={value} value={value}>
              {label}
            </TabsTrigger>
          ))}
        </TabsList>
        {PANELS.map(([value, , text]) => (
          <TabsContent key={value} value={value} className="ps-3">
            {text}
          </TabsContent>
        ))}
      </Tabs>
    </div>
  );
}
```

## Usage

```tsx
import {
  Tabs,
  TabsContent,
  TabsList,
  TabsTrigger,
} from '@workspace/active-ui/components/primitives/tabs';

<Tabs defaultValue="overview">
  <TabsList aria-label="Scheme">
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="documents">Documents</TabsTrigger>
  </TabsList>
  <TabsContent value="overview">…</TabsContent>
  <TabsContent value="documents">…</TabsContent>
</Tabs>;
```

## Anatomy

- `Tabs`: the value; `value` and `onValueChange` (controlled) or `defaultValue`. `orientation`
  `horizontal` (default) or `vertical`.
- `TabsList`: the tabs. `variant`: `default` (a filled track with the current tab raised) or
  `line` (a line under the current tab, for tabs in a page header). `activateOnFocus` makes the
  panel follow the focus as the arrow keys move; by default the arrow keys only move the focus.
- `TabsTrigger`: one tab, by its `value`; `disabled` skips it.
- `TabsContent`: the panel of a `value`. Only the current panel is rendered, unless `keepMounted`.
- `tabsListVariants({variant})`: the classes of a list, for an element of your own.

## What your application provides

- A name for the list (`aria-label`), the labels of the tabs and the panels.
- The value when it must survive a reload or be linked to: keep it in the URL, or use the composed
  [`SearchParamTabs`](/packages/active-ui/responsive-tabs).

## Accessibility and keyboard

- The list is one tab stop: `←` and `→` (or `↑` and `↓` when vertical) move the focus between
  the tabs, `Home` and `End` to the first and last, and `Enter` or `Space` shows the focused tab's
  panel (at once with `activateOnFocus`). `Tab` then moves into the panel.
- The list is a `role="tablist"`, each tab a `tab` with `aria-selected`, each panel a `tabpanel`
  labelled by its tab.

## API

### TabsList

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `variant` | `"default" \| "line" \| null` | no | `default` is a filled track with the current tab raised; `line` a line under the current tab. |
| `activateOnFocus` | `boolean` | no | Whether to automatically change the active tab on arrow key focus. Otherwise, tabs will be activated using <kbd>Enter</kbd> or <kbd>Space</kbd> key press. |
| `loopFocus` | `boolean` | no | Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. |
| `render` | `ComponentRenderFn<HTMLProps, TabsListState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

### Other parts

**Tabs**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `defaultValue` | `any` | no | The default value. Use when the component is not controlled. When the value is `null`, no Tab will be active. |
| `onValueChange` | `((value: TabsTab.Value, eventDetails: TabsRoot.ChangeEventDetails) => void)` | no | Callback invoked when new value is being set.  The event `reason` is `'none'` for user-initiated changes, such as a click or keyboard navigation; `'initial'` for the first automatic selection or fallback in uncontrolled roots when `defaultValue` is omitted or `undefined`, including when the implicit initial value is disabled or missing; `'disabled'` for automatic fallback when the selected tab becomes disabled in uncontrolled roots; or `'missing'` for automatic fallback when the selected tab is removed, or when an explicit `defaultValue` never matches a mounted tab in uncontrolled roots.  For automatic changes, the selected value can be `null` when no enabled Tab is available as a fallback.  Automatic changes cannot be canceled; calling `eventDetails.cancel()` for `'initial'`, `'disabled'`, or `'missing'` has no effect. |
| `orientation` | `Orientation` | no | The component orientation (layout flow direction). |
| `render` | `ComponentRenderFn<HTMLProps, TabsRootState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |
| `value` | `any` | no | The value of the currently active `Tab`. Use when the component is controlled. When the value is `null`, no Tab will be active. |

**TabsTrigger**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `value` | `any` | yes | The value of the Tab. |
| `disabled` | `boolean` | no | Whether the Tab is disabled.  If a first Tab on a `<Tabs.List>` is disabled, it won't initially be selected. Instead, the next enabled Tab will be selected. However, it does not work like this during server-side rendering, as it is not known during pre-rendering which Tabs are disabled. To work around it, ensure that `defaultValue` or `value` on `<Tabs.Root>` is set to an enabled Tab's value. |
| `nativeButton` | `boolean` | no | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `render` | `ComponentRenderFn<HTMLProps, TabsTabState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

**TabsContent**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `value` | `any` | yes | The value of the TabPanel. It will be shown when the Tab with the corresponding value is active. |
| `keepMounted` | `boolean` | no | Whether to keep the HTML element in the DOM while the panel is hidden. |
| `render` | `ComponentRenderFn<HTMLProps, TabsPanelState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

**tabsListVariants**: `(props?: (ConfigVariants<{ variant: { default: string; line: string; }; }> & ClassProp) \| undefined) => string`

> [!NOTE]
> **Based on shadcn/ui**
>
> This page adapts the Tabs documentation of shadcn/ui (MIT licence) to this
> package; see [Third-party notices](/third-party-notices).
