Tabs
Organize content into switchable panels triggered by a tab bar
Tabs and TabsNavigation share their visual styling but use different semantics. Use TabsNavigation when the controls scroll to page sections or filter an independent result list.
pnpm add @wandercom/design-system-web
Use Tabs when each trigger reveals one corresponding content panel. The compound components are built on Base UI's Tabs primitive and provide the expected tab roles, panel relationships, and keyboard behavior.
import {
TabsContent,
TabsIndicator,
TabsList,
TabsRoot,
TabsTrigger,
} from '@wandercom/design-system-web/ui/tabs';
export function Example() {
return (
<TabsRoot defaultValue="details">
<TabsList>
<TabsTrigger value="details">Details</TabsTrigger>
<TabsTrigger value="reviews">Reviews</TabsTrigger>
<TabsIndicator />
</TabsList>
<TabsContent value="details">Property details here.</TabsContent>
<TabsContent value="reviews">Guest reviews here.</TabsContent>
</TabsRoot>
);
}Use TabsNavigation when the controls scroll to page sections or filter an independent result list.
Pill-shaped tab triggers with a filled background on the active tab. Supports sm and md sizes.
<TabsRoot defaultValue="tab1" size="md" variant="default">
<TabsList>
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
<TabsIndicator />
</TabsList>
<TabsContent value="tab1">First panel.</TabsContent>
<TabsContent value="tab2">Second panel.</TabsContent>
</TabsRoot>Borderless tab triggers with an underline on the active tab. The size prop is ignored; height is fixed at 40px. Use the bordered prop on TabsList to add a full-width bottom border.
<TabsRoot defaultValue="tab1" variant="underline">
<TabsList bordered>
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
<TabsIndicator />
</TabsList>
<TabsContent value="tab1">First panel.</TabsContent>
<TabsContent value="tab2">Second panel.</TabsContent>
</TabsRoot>variant?
size?
defaultValue?
value?
onValueChange?
orientation?
className?
bordered?
className?
value
disabled?
className?
value
className?
Animated indicator that slides between active tabs. Place it as a child of TabsList. It renders a sliding bottom border for the underline variant. The default variant uses the active trigger's background and does not render a separate indicator.
className?
Built on Base UI's Tabs primitive, which follows the WAI-ARIA Tabs pattern.
Keyboard interaction:
ArrowLeft/ArrowRight- Navigate between tabs (horizontal orientation)ArrowUp/ArrowDown- Navigate between tabs (vertical orientation)Home- Move focus to the first tabEnd- Move focus to the last tabEnter/Space- Activate the focused tab
Only the active tab is in the tab sequence (tabIndex={0}). Inactive tabs use tabIndex={-1} so the user can tab past the entire tab list in one keystroke. Each tab is linked to its panel via aria-controls and aria-selected.
Reduced motion: The TabsIndicator slide animation respects prefers-reduced-motion: reduce. When the user's OS preference is set to reduce motion, the indicator transitions are disabled via motion-reduce:transition-none.
The high-level
Tabscomponent andTabsPropsare deprecated because they render tab semantics without corresponding panels. They remain temporarily available for patch compatibility. Do not use them for new work.
The old API is intentionally omitted from the live examples so the docs do not render an incomplete WAI-ARIA Tabs pattern. It remains documented here to help existing consumers identify and migrate it:
import { Tabs } from '@wandercom/design-system-web/ui/tabs';
<Tabs
items={[
{ label: "All reviews", value: "all" },
{ label: "Published", value: "published" },
{ label: "Drafts", value: "drafts" },
]}
onChange={setFilter}
value={filter}
/>Follow the legacy panel-less Tabs migration guide to move navigation and filtering controls to TabsNavigation. Compound TabsRoot, TabsList, TabsTrigger, TabsContent, and TabsIndicator are not deprecated.