Tabs
StableSwitches between views of the same object without leaving the page: the tokens, components and reviews of one library. The selected tab is marked with the signal underline.
When to use
Use it for
- Two to seven peer views of the same context, where users switch back and forth.
- underline for page and card sections; contained for a compact switch inside a toolbar (Light or Dark preview).
- count to show how many items wait in a view, like open reviews.
- Vertical orientation for settings pages with many sections next to their content.
Not for
- Steps of a sequence the user must complete in order: use a stepper or a form with sections.
- Navigation between pages with their own URL: use SidebarNav or links styled as tabs, not Tabs (which do not change the URL).
- Changing a setting or filter value: use RadioGroup or an attached ButtonGroup.
Do and don't
Every example is rendered with the real component, so it always matches what ships.
Short, parallel labels
One or two nouns per tab, same grammatical form. The user scans them as a set.
Tabs that are really actions
"Export" or "Delete" in a tab list hides an action behind a view switch. Use a Button.
Accessibility
- Base UI Tabs: role tablist, tab and tabpanel, aria-selected, aria-controls and aria-labelledby are wired for you.
- One tab stop for the whole list. Arrow keys move between tabs (Up and Down when vertical), Home and End jump, focus wraps by default.
- By default arrows move focus and Enter or Space selects; set activateOnFocus when panels are cheap to render.
- Give the TabsList an aria-label (or labelledby) so the tablist has a name.
- The selected tab has a visible signal underline plus stronger text: never color alone.
- Disabled tabs are announced as disabled and cannot be selected.
Anatomy and tokens
Read from packages/react/src/components/Tabs/Tabs.module.css at build time. Every value is a token: open one to see its
alias chain in both themes.
- Container
.root - Tab list
.list - sm
.sm - Tab button
.tab - Tab button, hover
@media (hover: hover) .tab:hover:not([data-disabled], [data-active]) - Tab button
.tab[data-active] - Tab button, focus
.tab:focus-visible - Tab button
.tab[data-disabled] - Count
.count - Tab button
.tab[data-active] .count - Underline variant
.underline - Underline variant
.underline::after - Underline variant
.underline .tab - Underline variant
.underline[data-orientation="vertical"]::after - Underline variant
.underline[data-orientation="vertical"] .tab - Selection indicator (signal underline or raised plate)
.indicator - Underline variant
.underline .indicator - Underline variant
.underline[data-orientation="vertical"] .indicator - Contained variant
.contained - Contained variant
.contained .tab - Contained variant
.contained.sm .tab - Contained variant
.contained .indicator - Panel
.panel - Panel, focus
.panel:focus-visible
Props
Parsed from the TypeScript types in packages/react/src/components/Tabs/Tabs.tsx, so this table cannot drift from the
code.
It also accepts every native <div> attribute except defaultValue, onChange.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'underline' | 'contained' | 'underline' | `underline` for page-level sections (signal underline); `contained` for a compact segmented switch. Default `underline`. |
size | 'sm' | 'md' | 'md' | Tab height follows density tokens. Default `md`. |
defaultValue | string | number | null | none | Value of the tab selected on first render (uncontrolled). |
value | string | number | null | none | Value of the selected tab (controlled). |
onValueChange | (value: TabsValue) => void | none | Called with the new value when the user selects a tab. |
orientation | 'horizontal' | 'vertical' | 'horizontal' | No description in the code yet. |
children | ReactNode | none | No description in the code yet. |
Playground
Change the props and see the real Tabs update. Controls are generated from the TypeScript props; nothing here changes the system.
<Tabs defaultValue="tokens" />Workbench
For contributors: every state with the accessibility panel, in the component workbench (Storybook). The stories double as visual test fixtures; the guidance on this page is the user documentation (open Tabs in Storybook) .
Embed Storybook stories here (4)
Loads from https://bridge-storybook.floriangouloubi.com (set STORYBOOK_URL at build time). If Storybook is not running, the frames stay
empty.
Underline
Contained
Vertical
AllStates
Changelog
- New
Navigation: Tabs (underline with a sliding signal indicator, contained, icons, counts, vertical), Breadcrumb (aria-current, collapsing middle levels), Pagination (full and compact, getPageItems helper) and SidebarNav (sections, collapsible groups, counts, current page mark).
Unreleased (current version 0.1.0, no release tagged yet) , Oct 17, 2026
- New
Bridge token system v1. Adds a brand mapping layer (`bridge` default, `ocean` alternate) between primitives and semantic tokens, a component tier (button, input, checkbox, switch, slider, card, popup, dialog, tooltip, toast, table, tabs, progress, nav), comfortable and compact density, motion durations and easings with a reduced-motion override, layers (z-index), icon sizes, breakpoints, a 1.2 type scale from 14px with tracking tokens, and self-hosted IBM Plex Sans and Mono (`@bridge-ds/tokens/fonts.css`). Primitives are regenerated in OKLCH on one lightness ladder. The build now writes `dist/resolved.json` and fails when any pair in `contrast.checks.json` drops below its WCAG minimum in any brand and theme. Fixes `color.border.strong` against `color.surface.default` (now at least 3:1 in both themes).
Unreleased (current version 0.1.0, no release tagged yet) , Oct 17, 2026