---
title: Tabs
description: A set of layered sections of content—known as tab panels—that are displayed one at a time.
icon: folder
author:
  name: shadcn
  url: https://github.com/shadcn
sidebar:
  badge: Updated
mode: wide
---

<PoweredBy
  packages={[
    { name: 'Base UI', url: 'https://base-ui.com/react/components/tabs' },
    { name: 'Shadcn UI', url: 'https://ui.shadcn.com/docs/components/tabs' },
  ]}
/>

### Installation

```package-install
npx shadcn@latest add @redpanda/tabs
```

The registry ships two visual styles for `TabsList` and `TabsTrigger`: the **default** segmented pill and the **underline** bar. Most previews on this page show both inline so you can compare them at a glance.

**Default**

<ComponentPreview name="tabs-demo" />

**Underline**

<ComponentPreview name="tabs-underline-demo" />

### Live appearance specimens

Selected, inactive and unavailable states use the component's actual behavior, not painted hover overrides. Hover an inactive tab, or move focus with the arrow keys and activate it with Enter or Space.

<ComponentPreview name="tabs-matrix-demo" />

## Variants

### Card

Use the `card` variant for tabs with a bordered card container.

**Default**

<ComponentPreview name="tabs-card-demo" />

**Underline**

<ComponentPreview name="tabs-card-underline-demo" />

### Contained

Use the `contained` variant for tabs with a muted background container.

<ComponentPreview name="tabs-contained-demo" />

## Sizes

### Small

Use `size="sm"` for compact tabs.

**Default**

<ComponentPreview name="tabs-small-demo" />

**Underline**

<ComponentPreview name="tabs-small-underline-demo" />

### Large

Use `size="lg"` for larger tabs.

**Default**

<ComponentPreview name="tabs-large-demo" />

**Underline**

<ComponentPreview name="tabs-large-underline-demo" />

## Layout

### Equal Layout

Use `layout="equal"` with `columns` to create equal-width tabs.

**Default**

<ComponentPreview name="tabs-equal-layout-demo" />

**Underline**

<ComponentPreview name="tabs-equal-layout-underline-demo" />

### Full Layout

Use `layout="full"` to make tabs span the full width.

**Default**

<ComponentPreview name="tabs-full-layout-demo" />

**Underline**

<ComponentPreview name="tabs-full-layout-underline-demo" />

### Gap

Use the `gap` prop to control spacing between tabs.

**Default**

<ComponentPreview name="tabs-gap-demo" />

**Underline**

<ComponentPreview name="tabs-gap-underline-demo" />

### Scrollable

When a horizontal tab strip has more triggers than fit the available width, use `ScrollableTabsList`
in place of `TabsList`. The strip stays on one line and becomes drag-scrollable instead of wrapping:
edge fades signal hidden tabs, a drag won't accidentally activate a tab, and keyboard arrow
navigation still scrolls the focused tab into view.

```tsx
import { ScrollableTabsList, Tabs, TabsTrigger } from '@/components/tabs';

<Tabs defaultValue="overview">
  <ScrollableTabsList variant="underline">{/* many triggers… */}</ScrollableTabsList>
  {/* panels… */}
</Tabs>;
```

`ScrollableTabsList` is a thin convenience wrapper: a `TabsList` pre-composed with
[`DragScrollArea`](/docs/drag-scroll-area) that supplies the content sizing (`w-max min-w-full`) and,
for the `underline` variant, a native content inset that protects the overhanging underline. The scroll viewport owns its masks and drag behavior; the bottom 4px remains opaque so the baseline stays crisp. If you need more control, compose
`DragScrollArea` around a plain `TabsList` directly — the wrapper just saves you that boilerplate.

<ComponentPreview
  name="tabs-scrollable-demo"
  description="An overflowing underline tab strip made drag-scrollable with ScrollableTabsList."
/>

This works with any variant. The edge fade is an alpha mask over the strip itself, so it blends on
any background. It pairs most naturally with `underline` (a nav-style strip), but the default
segmented variant scrolls too:

<ComponentPreview
  name="tabs-scrollable-pills-demo"
  description="The default segmented variant made drag-scrollable, with the alpha edge fade signalling hidden tabs."
/>

## Combinations

### Contained with Equal Layout

Combine contained variant with equal layout.

<ComponentPreview name="tabs-contained-equal-demo" />

### Combined with Toggle Group

This example demonstrates the proper relationship between tabs and toggle groups. Use tabs to navigate between distinct content areas, and toggle groups within each tab to filter or adjust that specific view's content.

<ComponentPreview
  name="tabs-with-toggle-group-demo"
  description="Real-world example showing tabs for navigation and toggle groups for filtering within each tab"
/>

## Patterns

### Keyboard activation

Tabs use manual keyboard activation by default: arrow keys move focus, then <kbd>Enter</kbd> or

<kbd>Space</kbd> activates the focused tab. This avoids triggering navigation or data fetching while someone is only
exploring the tab list.

Set `activateOnFocus` when every panel is available immediately and switching panels has no costly
side effects:

```tsx
<TabsList activateOnFocus>{/* triggers */}</TabsList>
```

### Vertical orientation

Underline triggers use an inset 3px keyboard focus ring so a horizontal scrollport cannot clip its top and bottom edges. Default segmented triggers retain their exterior ring.

Vertical tabs use the same standard segmented-pill recipe as horizontal tabs. Use `size="xl"` for the 16px list-to-panel gap, rather than overriding root spacing. Orientation supplies the column layout and left-aligned triggers.

<ComponentPreview
  name="tabs-vertical-demo"
  description="Tabs rendered vertically with left-aligned triggers for settings panels."
/>

### Disabled trigger with tooltip

Disabled tabs remain keyboard-focusable for discoverability, matching Base UI. Focus does not select them, and Enter or Space cannot activate them.

**Default**

<ComponentPreview
  name="tabs-disabled-with-tooltip-demo"
  description="Tabs with a disabled trigger explained via a Tooltip for gated features."
/>

**Underline**

<ComponentPreview
  name="tabs-disabled-with-tooltip-underline-demo"
  description="Underline tabs with a disabled trigger explained via a Tooltip for gated features."
/>

### Count badges

**Default**

<ComponentPreview
  name="tabs-with-count-badge-demo"
  description="Tabs with numeric badges on triggers for counts and activity indicators."
/>

**Underline**

<ComponentPreview
  name="tabs-with-count-badge-underline-demo"
  description="Underline tabs with numeric badges on triggers for counts and activity indicators."
/>

### Icon-only triggers

**Default**

<ComponentPreview name="tabs-icon-only-demo" description="Tabs with icon-only triggers for compact toolbars." />

**Underline**

<ComponentPreview
  name="tabs-icon-only-underline-demo"
  description="Underline tabs with icon-only triggers for compact toolbars."
/>

### As links

Pass a JSX element (or render function) to `TabsTrigger`'s `render` prop to swap the rendered element while keeping
keyboard, `data-state`, and active-highlight behavior intact.

**Default**

Bind `Tabs.value` to your router's active route when using router Link components.
The native-anchor examples below use real, per-instance panel fragments and keep
selection synchronized with browser Back and Forward. Their generated instance IDs
are not permanent application routes.

<ComponentPreview
  name="tabs-as-links-demo"
  description="Native fragment anchors navigate to their panels and follow browser history."
/>

**Underline**

<ComponentPreview
  name="tabs-as-links-underline-demo"
  description="Underline fragment anchors navigate to their panels and follow browser history."
/>

```tsx lineNumbers
<TabsList>
  <TabsTrigger render={<Link to="/overview" />} value="overview">
    Overview
  </TabsTrigger>
  <TabsTrigger render={<Link to="/billing" />} value="billing">
    Billing
  </TabsTrigger>
</TabsList>
```

## When to use

Tabs function as a navigation bar positioned atop content, allowing users to switch between distinct content views. Each tab displays unique content specific to that tab, without duplication across tabs. When a user interacts with a tab, it triggers the display of new content in a fresh view.

**Use tabs when:**

- Navigating between different, self-contained content areas
- Each section has unique content specific to that context
- Content changes completely when switching tabs
- Users need to switch between parallel information hierarchies

**Don't use tabs when:**

- Filtering or adjusting a single content view (use Toggle Group/Segmented Control instead)
- Content is shared or filtered across different states
- You need to show/hide portions of the same dataset

```mermaid
graph TD;
    START[What's the interaction?] --> NAV{Navigation between<br/>distinct content?};

    NAV -->|Yes - Different views| TABS[✅ Use Tabs];
    NAV -->|No - Same view filtering| SEG[⚠️ Use Toggle Group<br/>Segmented Control];

    TABS --> CONTENT{Content type?};
    CONTENT -->|Unique per tab| UNIQUE[✅ Each tab shows<br/>different content];
    CONTENT -->|Shared/filtered| SHARED[⚠️ Use Toggle Group instead];

    UNIQUE --> STYLE{Visual style?};
    STYLE -->|Contained/Card| CARD[✅ Card variant];
    STYLE -->|Minimal| UNDER[✅ Underline variant];
    STYLE -->|Background| CONT[✅ Contained variant];

    SEG --> TOGGLE[Toggle Group filters<br/>or adjusts current view];
    SHARED --> TOGGLE;

    classDef recommended fill:#4ade80,stroke:#22c55e,stroke-width:2px,color:#000;
    classDef discouraged fill:#fbbf24,stroke:#f59e0b,stroke-width:2px,color:#000;
    classDef decision fill:#60a5fa,stroke:#3b82f6,stroke-width:2px,color:#fff;
    classDef info fill:#8b5cf6,stroke:#7c3aed,stroke-width:2px,color:#fff;

    class TABS,UNIQUE,CARD,UNDER,CONT recommended;
    class SEG,SHARED discouraged;
    class START,NAV,CONTENT,STYLE decision;
    class TOGGLE info;
```

## Usage

```tsx
<Tabs>
  <TabsList>
    <TabsTrigger value="tab1">Tab 1</TabsTrigger>
    <TabsTrigger value="tab2">Tab 2</TabsTrigger>
  </TabsList>
  <TabsContents>
    <TabsContent value="tab1">Tab 1 Content</TabsContent>
    <TabsContent value="tab2">Tab 2 Content</TabsContent>
  </TabsContents>
</Tabs>
```

### Underline Variant

```tsx
<Tabs>
  <TabsList variant="underline">
    <TabsTrigger variant="underline" value="tab1">
      Tab 1
    </TabsTrigger>
    <TabsTrigger variant="underline" value="tab2">
      Tab 2
    </TabsTrigger>
  </TabsList>
  <TabsContents>
    <TabsContent value="tab1">Tab 1 Content</TabsContent>
    <TabsContent value="tab2">Tab 2 Content</TabsContent>
  </TabsContents>
</Tabs>
```

## Props

<div className="flex flex-row gap-x-3">
  <ExternalLink href="https://base-ui.com/react/components/tabs" text="Docs" />
  <ExternalLink href="https://base-ui.com/react/components/tabs#api-reference" text="API Reference" />
</div>

### Registry Props

#### Tabs

| Prop | Type | Default | Description |
| - | - | - | - |
| `size?` | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | The size of the tabs container |
| `variant?` | `'default' \| 'card' \| 'contained'` | `'default'` | The visual variant of the tabs container |

#### TabsList

| Prop | Type | Default | Description |
| - | - | - | - |
| `variant?` | `'default' \| 'underline'` | `'default'` | The visual variant of the tabs list |
| `layout?` | `'auto' \| 'equal' \| 'full'` | `'auto'` | The layout of the tabs list |
| `gap?` | `'none' \| 'sm' \| 'md' \| 'lg'` | `'none'` | The gap between tabs |
| `columns?` | `number` | - | Number of columns for equal layout |
| `activeClassName?` | `string` | - | The class name to apply to the active tab highlight (e.g. to customize its CSS transition) |
| `activateOnFocus?` | `boolean` | `false` | Whether focusing a tab with arrow keys also activates it |

#### TabsTrigger

| Prop | Type | Default | Description |
| - | - | - | - |
| `variant?` | `'default' \| 'underline'` | `'default'` | The visual variant of the tabs trigger |

#### TabsContents

| Prop | Type | Default | Description |
| - | - | - | - |
| `children` | `React.ReactNode` | - | The children of the tabs contents |
| `className?` | `string` | - | The class name to apply to the tabs contents |

## Keyboard interactions

| Key                        | Action                                                |
| -------------------------- | ----------------------------------------------------- |
| `Tab` into `TabsList`      | Focus lands on the active tab.                        |
| `ArrowLeft` / `ArrowRight` | Moves focus between tabs (horizontal orientation).    |
| `ArrowUp` / `ArrowDown`    | Moves focus between tabs (vertical orientation).      |
| `Home` / `End`             | Jumps to first / last tab.                            |
| `Space` / `Enter`          | Activates the focused tab in the default manual mode. |

## Accessibility

- `TabsList` carries `role="tablist"`, each `TabsTrigger` is `role="tab"` with `aria-selected`, and each `TabsPanel` is
  `role="tabpanel"` wired via `aria-controls` / `aria-labelledby`.
- Focus is roving inside `TabsList` — only the active tab is in the tab order; the others are reachable via arrow keys.
- Tabs activate manually by default; pass `activateOnFocus` to `TabsList` only when every panel is immediately available.
- When wrapped in [`DragScrollArea`](/docs/drag-scroll-area), arrow-key navigation scrolls the focused tab into view, so
  all tabs stay keyboard-reachable even while some are visually clipped.
- Use the `underline` variant sparingly when tabs live next to inline links — the underline can be mistaken for a link.

## Credits

- We use [Base UI](https://base-ui.com/react/components/tabs) for the tabs component.
- We take our inspiration from [Shadcn UI](https://ui.shadcn.com/docs/components/tabs) for the tabs style.
