Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Tabs

A set of layered sections — line or pill variants, optional icons and count badges, horizontal or vertical orientation, full keyboard navigation.

Status
stable
Since
0.1.0
Accessibility pattern
APG tabs

Last updated

A high-level summary of your workspace.

Install

Add Tabs from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/tabs

Usage

import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";

<Tabs defaultValue="overview">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity" count={3}>
      Activity
    </TabsTrigger>
  </TabsList>
  <TabsContent value="overview">Overview content</TabsContent>
  <TabsContent value="activity">Activity content</TabsContent>
</Tabs>;

Anatomy

Tabs is a compound component built on Base UI Tabs. Compose the parts inside the root, matching each TabsTrigger to a TabsContent by value:

Tabs — data-slot="tabs"
TabsContent — data-slot="tabs-content"
TabsList — data-slot="tabs-indicator" | "tabs-list"
TabsTrigger — data-slot="tabs-trigger" | "tabs-trigger-count"
<Tabs defaultValue="overview" orientation="horizontal">
  <TabsList variant="line">
    <TabsTrigger value="overview">
      <Icon />
      Overview
    </TabsTrigger>
    <TabsTrigger value="activity" count={3}>
      Activity
    </TabsTrigger>
  </TabsList>
  <TabsContent value="overview">…</TabsContent>
  <TabsContent value="activity">…</TabsContent>
</Tabs>
  • Tabs — the root that groups the list and panels and owns orientation (data-slot="tabs", data-orientation). Controlled via value/onValueChange, or uncontrolled via defaultValue.
  • TabsList — the row (or column) of triggers (data-slot="tabs-list", data-variant). Hosts the moving underline indicator for the line variant.
  • TabsTrigger — an individual tab button (data-slot="tabs-trigger"). Active state is exposed as data-active. Compose a leading icon as the first child; pass count for a trailing badge.
  • TabsContent — the panel shown for the active tab (data-slot="tabs-content"), matched by value.

Examples

Variants

line (a moving underline tracks the active tab, the default) and pill (a bounded muted track where the active tab raises on the shared selected-chip recipe). Both support a leading icon composed as the first child and a trailing count badge.

On line, the trigger's hover wash is deliberately held one 4px step off the rule the underline rides along — a wash that runs flush into a container hairline reads as a rendering bug rather than a state. The gap is a logical margin, so the vertical variant mirrors it onto the inline-start rail and RTL follows without a second rule.

Line variant — a moving underline tracks the active tab.
Pill variant — the active tab becomes a raised chip on a muted track.

Orientation

Set orientation="vertical" on the root to stack the tab list beside the panels — useful for settings-style navigation. Keyboard navigation switches to the up/down arrow keys automatically. With the line variant, the moving underline indicator rides the inline-start rail instead of the bottom rule, so it mirrors in RTL:

Vertical + line — the moving underline indicator rides the left rail.

The pill variant stacks the raised-chip triggers in a column:

Your public profile and avatar.

Disabled

Mark a TabsTrigger disabled to render it dimmed and non-interactive. It carries data-disabled, is skipped by keyboard navigation, and cannot be selected:

A high-level summary of your workspace.

Chip variant

variant="chip" renders free-standing tabs on the 28px chrome scale — no track, no underline; the active tab raises on the same selected-chip recipe pill uses — the pressed/selected rung with the one hairline, and it keeps its own hover and pressed steps. The dense record-page treatment; pair with count for per-tab totals. For tab sets that outgrow their container, compose an overflow menu ("+N more") from DropdownMenu — the list itself scrolls with an edge fade by default.

Record overview panel.

Playground

Switch between the variants and orientations, then copy the generated JSX.

Overview content
<Tabs defaultValue="overview">
  <TabsList>
    <TabsTrigger value="overview">Overview</TabsTrigger>
    <TabsTrigger value="activity">Activity</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="overview">Overview content</TabsContent>
  <TabsContent value="activity">Activity content</TabsContent>
  <TabsContent value="settings">Settings content</TabsContent>
</Tabs>

API Reference

Tabs

PropTypeDefaultDescription
orientation"horizontal" | "vertical"'horizontal'Layout flow direction. horizontal lays the tab row above the panels; vertical stacks the tab list beside the panels.

Data attributes and CSS variables on Tabs

AttributeValues
data-slot"tabs"

TabsList

PropTypeDefaultDescription
variant"chip" | "line" | "pill"'line'Active-tab treatment. - line: transparent track with a moving underline indicator (default). - pill: muted track; the active tab raises on the shared selected-chip recipe (selectedChipVariants) it holds in common with Segmented. - chip: the same raised chip free-standing, with no track (the dense record-page treatment).

Data attributes and CSS variables on TabsList

AttributeValues
data-slot"tabs-indicator" | "tabs-list"
data-variantmirrors a prop or state value

TabsTrigger

PropTypeDefaultDescription
countnumberOptional count rendered as a trailing badge — e.g. unread or item totals. Painted as body ink on a quiet ink wash, so it reads on every variant and state.

Data attributes and CSS variables on TabsTrigger

AttributeValues
data-slot"tabs-trigger" | "tabs-trigger-count"

TabsContent

TabsContent adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

Data attributes and CSS variables on TabsContent

AttributeValues
data-slot"tabs-content"

Accessibility

  • Built on Base UI Tabs: the list renders with role="tablist", each trigger with role="tab", and each panel with role="tabpanel", all wired together with aria-controls/aria-selected.
  • Roving tabindex — only the active tab is in the tab order; arrow keys move between tabs within the list.
  • Activation is manual by default: arrow keys move focus, Enter/Space selects.
  • Every interactive part shows a visible :focus-visible ring (outline-ring) — never outline: none.
  • Disabled triggers carry data-disabled, are skipped by keyboard navigation, and are not selectable.
KeyAction
TabMove focus into the tab list (lands on the active tab), then out to the panel.
/ Move focus between tabs (horizontal orientation).
/ Move focus between tabs (vertical orientation).
Home / EndMove focus to the first / last tab.
Enter / SpaceActivate the focused tab.
ContractStates tested
Behaviourdefault, active, disabled, selected
Accessibilitydisabled, focus-visible
Visualdefault, hover, focus, active, disabled

Do / Don't

Do
Keep tab labels short and parallel, match each trigger to a panel by value, and use a count badge to surface pending items.
Don't
Use tabs for sequential steps (use a stepper/wizard) or nest tabs more than one level deep.

On this page