npx shadcn@latest add https://sdk-components.thesqd.com/r/tabs.jsonimport { Tabs } from "@/components/ui/tabs";import { Tabs } from "@/components/ui/tabs";
export function Example() {
return (
<Tabs
defaultValue="account"
items={[
{ value: "account", label: "Account", content: "Make changes to your account." },
{ value: "password", label: "Password", content: "Change your password." },
]}
/>
);
}Tabs (single component — pass items as data)
├── variant="default" | "segment" | "underline" | "underline-card" | "rounded"
├── size="default" | "sm" | "lg" (sm = h-8, compact)
├── align="start" | "center" | "end" (underline variants)
├── addItems / onAddItem / addTooltip (trailing "+" button)
├── subNav="dropdown" | "rail" (subItems as a caret menu, or a
│ vertical rail inside the panel)
├── items={[
│ { value, label, icon?, content?, subItems?, defaultSubValue?, disabled?, triggerClassName?, contentClassName? },
│ …
│ ]}
└── ...rest (defaultValue / value / onValueChange / orientation)
Auto-emits TabsList + TabsTrigger per item; TabsContent per item that has
`content`. Items without `content` render a trigger only — useful for
toolbar-style segmented controls (e.g. a theme switcher).
Need a custom panel layout, asynchronous content loading, or a TabsList
sandwiched between hand-written rows? The slot exports — TabsList,
TabsTrigger, TabsContent — remain available as the escape hatch.Last sub item picked: —
Conversations and threads.
Tabs
| Prop | Type | Default | Description |
|---|---|---|---|
items | TabsItem[] | — | List of tab specs. Without this prop, Tabs stays compound (drop in TabsList + TabsTrigger + TabsContent children). |
variant | "default" | "segment" | "underline" | "underline-card" | "rounded" | "default" | Pill container, compact segmented control, sliding underline nav, the same underline nav with each panel in a card container below it, or Notion-style rounded icon tabs that expand the active label. |
align | "start" | "center" | "end" | "start" | Where the triggers sit along the strip. Underline variants only — the pill variants are width-fit, so alignment is the parent's job. |
panelClassName | string | — | `underline-card` only — extra classes for the card panel wrapping each item's content (padding defaults to `p-4`). |
addItems | TabsSubItem[] | — | Adds a trailing **+** button to the strip that opens a menu of these options. Omit it (but keep `onAddItem`) and the + is a plain button with no menu. |
onAddItem | (value?: string) => void | — | Fires with the chosen option's value — or with nothing when there's no menu. Append to your own `items` array here; Tabs never adds a tab itself. |
addTooltip | React.ReactNode | "Add tab" | Hover tooltip and accessible label for the + button. |
subNav | "dropdown" | "rail" | "dropdown" | How a tab's `subItems` are presented — a caret menu on the trigger, or a vertical underline rail inside the panel. Both read the same data and report through `onSubValueChange`. |
subNavListClassName | string | — | `subNav="rail"` only — classes for the rail's list. Width lives here; defaults to `w-48`. |
size | "default" | "sm" | "lg" | "default" | Tab scale. `sm` shrinks the pill list to `h-8` for compact, inline filter rows, and gives the underline variants a smaller label, smaller icons and a tighter row. `lg` is the prominent nav-style scale. |
fluid | boolean | false | Fill the available width with content-proportional tabs — each trigger grows/shrinks from its natural label width (flex-auto), so long labels keep more room than short ones when space is tight. Great for full-width tab rows on mobile. Applies to `default` and `segment` variants. |
defaultValue | string | — | Uncontrolled initial active tab. |
value | string | — | Controlled active tab value. |
onValueChange | (value: string) => void | — | Fires when the active tab changes. |
orientation | "horizontal" | "vertical" | "horizontal" | Drives flex direction and arrow-key navigation. |
listClassName | string | — | Class names applied to the auto-emitted `TabsList`. |
TabsItem
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Identifier matching a `TabsContent` panel. |
label | ReactNode | — | Trigger label. |
icon | ReactNode | — | Leading icon rendered before the label. |
content | ReactNode | — | Panel content. Omit to render a trigger-only tab (e.g. for segmented control patterns). |
subItems | TabsSubItem[] | — | Sub-navigation for this tab: `{ value, label, icon?, disabled? }`. Adds a caret that opens the menu; tapping the tab itself jumps to the first sub item, and pressing and holding it opens the menu. |
defaultSubValue | string | — | Uncontrolled initial sub item for this tab. Defaults to the first entry in `subItems`. |
onSubValueChange | (value: string) => void | — | Fires when this tab's sub item changes — from the caret menu or from tapping the tab (which selects the first sub item). |
disabled | boolean | — | Skip this tab in keyboard navigation. |
triggerClassName | string | — | Per-tab trigger class names. |
contentClassName | string | — | Per-tab panel class names. |