Skip to content
shadcn.io
shadcn.io is not affiliated with official shadcn/ui

Shadcn Tabs for React and Tailwind

Mutually exclusive panels in one region.

Profile

Manage your profile information

View you shared information here, update you profile name, picture and email.

Installation

bunx --bun shadcn@latest add https://kit.dev/r/tabs.json

Anatomy

Tabs
├── TabsList
│   ├── TabsTrigger
│   └── Indicator (built in when variant="underline")
└── TabsContent

Usage

import {
  Tabs,
  TabsList,
  TabsTrigger,
  TabsContent,
} from "@/components/ui/tabs";
<Tabs defaultValue="profile">
  <TabsList>
    <TabsTrigger value="profile">Profile</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="profile">Profile content</TabsContent>
  <TabsContent value="settings">Settings content</TabsContent>
</Tabs>

Each trigger value must match a content value.

Controlled

Use value and onValueChange to control the active tab.

Root Provider

Use useTabs with TabsRootProvider when you need the API outside the tree. Pass machine options (defaultValue, orientation, activationMode, …) to useTabs(), not to the provider.

Variant

Set variant on TabsList. The underline variant renders the selection indicator.

Default

Underline

Orientation

Set orientation on Tabs. Horizontal uses Left/Right keys; vertical uses Up/Down.

Horizontal

Vertical

States

Disabled

Disable a trigger with disabled. It is not focusable or selectable.

Examples

Vertical with underline

Combine orientation="vertical" on Tabs with variant="underline" on TabsList.

With icons

Context

Read the selected value with TabsContext or useTabsContext.

Render triggers as links with asChild. Pair with navigate on the root for in-app routing.

Lazy mount

shadcn.io defaults lazyMount and unmountOnExit to true. Panels mount the first time they are selected.

Manual activation

activationMode="manual" moves focus with the arrow keys but only selects on Enter or Space (or pointer).

Guides

Bottom navigation

For a mobile bar fixed to the viewport, use Bottom Navigation. It is Tabs with different parts and styles.

<Tabs
  navigate={(details) => {
    router.push(details.node.href);
  }}
>

NavigateDetails is { value: string; node: HTMLAnchorElement; href: string }.

API Reference

shadcn.io wraps Ark UI Tabs. Defaults below are shadcn.io values. lazyMount and unmountOnExit default to true (Ark: false).

asChild merges props onto a single child element.

Tabs

Root. Renders a div.

PropTypeDefaultDescription
activationMode"automatic" | "manual""automatic"automatic selects on focus. manual selects on click / Enter / Space.
asChildbooleanfalseRender the child element instead of a div.
classNamestring-Class names on the root.
compositeboolean-Treat as composed with other composite widgets.
defaultValuestring | null-Uncontrolled selected tab.
deselectableboolean-Allow clicking the active tab to clear selection.
hideMode"display-none" | "activity""display-none"How to hide mounted-but-inactive content. activity needs React 19+.
idstring-Unique id for the machine.
idsPartial<{ root: string; trigger: (value: string) => string; list: string; content: (value: string) => string; indicator: string }>-Element ids for composition.
lazyMountbooleantrueMount a panel the first time it is selected.
loopFocusbooleantrueLoop keyboard focus from last to first trigger.
navigate(details: NavigateDetails) => void-Called when a link trigger is chosen. { value, node, href }.
onFocusChange(details: FocusChangeDetails) => void-Called when focus moves. { focusedValue: string }.
onValueChange(details: ValueChangeDetails) => void-Called when the selected tab changes. { value: string }.
orientation"horizontal" | "vertical""horizontal"Layout and arrow-key direction.
translations{ listLabel?: string }-Localized strings for the tablist.
unmountOnExitbooleantrueUnmount a panel after it is hidden.
valuestring | null-Controlled selected tab.
AttributeDescription
data-slottabs
data-scopetabs
data-partroot
data-orientation"horizontal" or "vertical"
data-focusPresent when the tablist is focused

TabsList

Container for triggers. Renders a div with role="tablist". The underline variant includes the indicator.

PropTypeDefaultDescription
variant"default" | "underline""default"default is a pill on the selected trigger. underline draws a bar via --width / --height.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the list.
AttributeDescription
data-slottabs-list
data-scopetabs
data-partlist
data-orientation"horizontal" or "vertical"

Underline indicator:

AttributeDescription
data-slottab-indicator
data-scopetabs
data-partindicator
data-orientation"horizontal" or "vertical"
CSS variableDescription
--widthWidth of the selected trigger
--heightHeight of the selected trigger
--left / --topIndicator position (also exposed as translate)

TabsTrigger

Selects a tab. Renders a button with role="tab".

PropTypeDefaultDescription
valuestringrequiredId that must match a TabsContent.
disabledboolean-Disable this trigger.
asChildbooleanfalseMerge onto a single child (for example a or Link).
classNamestring-Class names on the trigger.
AttributeDescription
data-slottabs-trigger
data-scopetabs
data-parttrigger
data-selectedPresent when this tab is selected
data-disabledPresent when disabled
data-focusPresent when focused
data-orientation"horizontal" or "vertical"
aria-selected"true" or "false"

TabsContent

Panel shown when its trigger is selected. Renders a div with role="tabpanel".

PropTypeDefaultDescription
valuestringrequiredId that must match a TabsTrigger.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the panel.
AttributeDescription
data-slottabs-content
data-scopetabs
data-partcontent
data-selectedPresent when this panel is selected
data-orientation"horizontal" or "vertical"

TabsRootProvider

Root alternative that takes the API from useTabs. Renders a div.

PropTypeDefaultDescription
valueUseTabsReturnrequiredReturn value of useTabs().
asChildbooleanfalseRender the child element instead of a div.
classNamestring-Class names on the root.
hideMode"display-none" | "activity""display-none"How to hide mounted-but-inactive content.
lazyMountbooleantrueMount a panel the first time it is selected.
unmountOnExitbooleantrueUnmount a panel after it is hidden.

Pass defaultValue, orientation, activationMode, and other machine options to useTabs(), not to TabsRootProvider.

AttributeDescription
data-slottabs
data-scopetabs
data-partroot

useTabs

Creates the tabs API for TabsRootProvider. Accepts the same options as Tabs except layout-only props.

const tabs = useTabs({ defaultValue: "profile" });
tabs.setValue("settings");

TabsContext / useTabsContext

Render-prop or hook access to tabs state. Use inside Tabs or TabsRootProvider.

PropertyTypeDescription
valuestring | nullSelected tab.
focusedValuestring | nullFocused trigger.
setValue(value: string) => voidSelect a tab.
clearValue() => voidClear the selection.
focus() => voidFocus the selected trigger.
selectNext(fromValue?: string) => voidSelect the next tab.
selectPrev(fromValue?: string) => voidSelect the previous tab.
getTriggerState(props: { value: string; disabled?: boolean }) => TriggerStateState for one trigger (selected, disabled, focused).

TabsContext children: (context) => ReactNode.

Accessibility

Complies with the Tabs WAI-ARIA design pattern. Only the selected panel is in the tab order (unless you keep others mounted and visible).

Keyboard support

KeyDescription
TabMoves into the tablist (active trigger) or out to the next control.
ArrowRightNext trigger (horizontal). Selects it when activationMode is automatic.
ArrowLeftPrevious trigger (horizontal).
ArrowDownNext trigger (vertical).
ArrowUpPrevious trigger (vertical).
HomeFirst trigger.
EndLast trigger.
Enter / SpaceSelect the focused trigger (manual activation, and always via pointer).