Skip to content
Redpanda UI
Esc
↑↓navigate↵open⌘Jpreview
Made by shadcn
Edit on GitHub

Tabs

A set of layered sections of content—known as tab panels—that are displayed one at a time.

Installation

bash
npx shadcn@latest add @redpanda/tabs
bash
pnpm dlx shadcn@latest add @redpanda/tabs
bash
yarn dlx shadcn@latest add @redpanda/tabs
bash
bunx shadcn@latest add @redpanda/tabs
bash
nubx shadcn@latest add @redpanda/tabs
bash
aube dlx 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

Underline

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.

Variants

Card

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

Default

Underline

Contained

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

Sizes

Small

Use size="sm" for compact tabs.

Default

Underline

Large

Use size="lg" for larger tabs.

Default

Underline

Layout

Equal Layout

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

Default

Underline

Full Layout

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

Default

Underline

Gap

Use the gap prop to control spacing between tabs.

Default

Underline

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

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:

Combinations

Contained with Equal Layout

Combine contained variant with equal layout.

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.

Patterns

Keyboard activation

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

Space 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.

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

Underline

Count badges

Default

Underline

Icon-only triggers

Default

Underline

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.

Underline

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

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

Registry Props

Tabs

PropType
size?'sm' | 'md' | 'lg' | 'xl' | 'full'

The size of the tabs container

Type'sm' | 'md' | 'lg' | 'xl' | 'full'
Default'md'
variant?'default' | 'card' | 'contained'

The visual variant of the tabs container

Type'default' | 'card' | 'contained'
Default'default'

TabsList

PropType
variant?'default' | 'underline'

The visual variant of the tabs list

Type'default' | 'underline'
Default'default'
layout?'auto' | 'equal' | 'full'

The layout of the tabs list

Type'auto' | 'equal' | 'full'
Default'auto'
gap?'none' | 'sm' | 'md' | 'lg'

The gap between tabs

Type'none' | 'sm' | 'md' | 'lg'
Default'none'
columns?number

Number of columns for equal layout

Typenumber
activeClassName?string

The class name to apply to the active tab highlight (e.g. to customize its CSS transition)

Typestring
activateOnFocus?boolean

Whether focusing a tab with arrow keys also activates it

Typeboolean
Defaultfalse

TabsTrigger

PropType
variant?'default' | 'underline'

The visual variant of the tabs trigger

Type'default' | 'underline'
Default'default'

TabsContents

PropType
childrenReact.ReactNode

The children of the tabs contents

TypeReact.ReactNode
className?string

The class name to apply to the tabs contents

Typestring

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, 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 for the tabs component.
  • We take our inspiration from Shadcn UI for the tabs style.

Recent changes

  • patchv1.2.0Pin shipped dependency floors to the version we develop against. Registry items now declare ranges like `^5.1.9` (the actual installed version) instead of collapsing to `^5.0.0`, so consumers start on the known-tested baseline while caret semantics still allow any compatible release within the same major.#133
  • patchv1.2.0Polish the Tabs active-indicator animation. The highlight now slides strictly along the tab strip's axis (left/right for horizontal tabs, up/down for vertical tabs) instead of drifting diagonally when tabs wrap onto multiple rows or reflow. Eliminates a small opacity flicker that occurred at the end of each tab switch.#126
  • minorv1.1.0Theme docs refresh, readability pass on semantic foregrounds, and consumer-facing Base UI regression fixes.#121
  • minorv1.0.0Post-Base-UI polish. Public API unchanged.#116
  • majorv1.0.0Migrate every Radix-based primitive to `@base-ui/react@^1.4.0` (Base UI).#114
See full history →

Last updated on

Was this page helpful?