UI.MD

Tabs

A set of tabbed panels with a sliding pill indicator animated by Motion.

Installation

$
npx shadcn@latest add https://ui.mattdowney.com/r/tabs.json

Usage

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

Examples

Horizontal

Default

Each TabsTrigger is tied to its panel by a matching value string. Mistype either one and the tab still selects, the panel just comes up blank, with nothing to tell you why.

Sizes

size steps the tabs along the control scale: sm (h-7, 12px), default (h-8, 13px), lg (h-10, 14px). One prop on Tabs sizes the triggers, the list, and the count chips. The height sits on the trigger, so a tab lines up with a Nav item of the same size; the list adds its 4px gutter on top.

Small
Default
Large

With Icons

Pass an icon to a trigger to render it before the label.

With Count

Pass a count to add a counter chip. It fills solid accent on the active tab. Add an aria-label where a bare number needs context (aria-label="Open, 18 issues").

With Icons and Count

icon and count compose. Cap large counts at "99+" so a growing number can't widen the tab.

Icon Only

Omit the children for an icon-only trigger; the pill tightens to a square. Each text-less trigger needs an aria-label for an accessible name.

Vertical

Default

orientation="vertical" stacks the triggers into a column beside the panels instead of above them, the settings-pane pattern. The gap moves to the inline axis with it, so the list takes mr-3 where a horizontal one takes mb-3. Labels align to the leading edge, and the sliding indicator travels vertically.

Sizes

The same three rungs as a horizontal strip, and the same heights: sm (h-7, 12px), default (h-8, 13px), lg (h-10, 14px). One prop on Tabs sizes the triggers, the labels, and the count chips in either orientation, so a vertical strip lines up with a Nav sidebar of the same size.

With Count

A count sits at the trailing edge of a vertical trigger, not against the label, so a column of numbers reads as a column. That needs a width on the TabsList: the strip sizes to its own content, and there is no shared trailing edge to push a count to until you give it one.

API Reference

PropTypeDefaultDescription
defaultValueTabsstring-The tab that is active when initially rendered, for uncontrolled usage.
valueTabsstring-The controlled active tab. Pair with onValueChange.
onValueChangeTabs(value: string) => void-Called with the new value when the active tab changes.
sizeTabs"sm" | "default" | "lg""default"Steps the triggers, list, and count chips along the control scale, from sm (h-7, 12px type) up to lg (h-10, 14px type). The height sits on the trigger, matching Nav item for item; the list adds its 4px gutter on top.
orientationTabs"horizontal" | "vertical""horizontal"Axis of the tab strip. vertical stacks the triggers into a column and moves the strip beside the panels instead of above them; the sliding indicator travels vertically to match.
value*TabsTriggerstring-Identifier linking each <TabsTrigger /> to its matching <TabsContent />.
fullWidthTabsListbooleanfalseStretches the list to fill its container so triggers share the width evenly. Horizontal lists share the width between triggers; a vertical list sizes each row to its content.
iconTabsTriggerReact.ReactNode-Leading icon rendered before the trigger label, e.g. <Star />.
countTabsTriggerReact.ReactNode-Trailing counter chip, e.g. 18 or "99+". Fills solid accent on the active tab.