Tabs
The row of tabs.
Beta · Navigation and overlay · packages/css/src/components/tabs.css · React on Base UI: @pepperui/tabs
.pui-tabs (line) · --segment · --small · __trigger with aria-selected · __icon · --on-card.
Sizes across, states down.
What it is for
Section titled “What it is for”- Variant line / segment × Size small / medium
- The segment’s box follows the Ground mode of the frame it sits in (page · card); line has no box to fill
- Three exposed triggers — edit labels and states on the instance; hide one for a pair
- The first trigger is active by default; keep exactly one active
- The content below is not a component — it is what the tab opens
Use when one of several views must be open — sections of a page (line), or a two-to-four-way mode (segment).
Do not use when the options are actions (Button group), multi-select filters (Chip), or top-level navigation (Sidebar, Topbar).
Relates to Tabs trigger - line, Tabs trigger - segment, Button group, Toggle group, Chip, Sidebar.
Anti-patterns two lists on one card · a list with nothing active · a segment with five options · tabs that scroll · a white box painted by hand on a product screen instead of the frame’s Ground mode.
Keywords: tabs, tablist, segment, strip, sections, panes, mode, ground
Figma — page Tabs: Tabs list (4 variants) · Tabs trigger - line (10 variants) · Tabs trigger - segment (10 variants)
shadcn: tabs
The markup — copy this
Section titled “The markup — copy this”The class, the parts, and nothing that is not in the package. The same block opens tabs.css.
<div class="pui-tabs" role="tablist"> <button class="pui-tabs__trigger" role="tab" aria-selected="true">Charts</button> <button class="pui-tabs__trigger" role="tab">Images</button></div><div class="pui-tabs pui-tabs--segment" role="tablist"> …the same triggers… </div>The modifiers
Section titled “The modifiers”| What it is | Note | |
|---|---|---|
| Variant | (none) line · --segment | — |
| Size | --small (12 px) · (none) medium (14 px) | — |
| On card | automatic: under [data-ground=“card”] and inside every part that paints surface/card (ground.css) the box is white; in a white Card it is neutral/100, as on any white ground. --on-card is the explicit switch, for a consumer who paints the product ground without setting data-ground. Segment only; line has no box to fill | — |
| State | active = [aria-selected=“true”] or [data-state=“active”] · :hover · :focus-visible · :disabled | — |
| Parts | __trigger · __icon (line only — a 16 px leading glyph) | — |
Accessibility — measured, not assumed
Section titled “Accessibility — measured, not assumed”Every number here is measured on the rendered component: contrast (npm run contrast:render), the focus ring (npm run focus) and the keyboard (npm run keyboard). The floors are principle 12’s.
Contrast
Section titled “Contrast”| ink on ground | reads | floor | the matrix calls it | |
|---|---|---|---|---|
#544d4d on #f2f2f2 |
7.36 | 4.5 | text.muted on surface.sunken (promised) | ok |
#544d4d on #ffffff |
8.24 | 4.5 | text.muted on surface.page (promised) | ok |
#ffffff on #231b1b |
16.88 | 4.5 | text.on-accent on surface.inverse (not-offered) | ok |
#0b0202 on #ffffff |
20.51 | 4.5 | text.default on surface.page (promised) | ok |
4 distinct pair(s) rendered, 0 that axe calls a violation.
The focus ring
Section titled “The focus ring”| primitive | on | the indicator | over | the change | |
|---|---|---|---|---|---|
| Tabs trigger | surface/page | shadow #6a66e4 2 px |
#ffffff |
4.53 | ok |
| Tabs trigger | surface/card | shadow #6a66e4 2 px |
#f2f2f7 |
4.06 | ok |
| Tabs trigger | surface/sunken | shadow #6a66e4 2 px |
#f2f2f2 |
4.05 | ok |
15 of 15 focus stop(s) in this block meet WCAG 2.4.13 on screen — the same crop screenshotted focused and unfocused, and the difference measured.
Keyboard
Section titled “Keyboard”| role | tab stops | what the pattern requires | |
|---|---|---|---|
tablist .pui-tabs |
1 | exactly 1 | ok |
tablist .pui-tabs.pui-tabs--small |
1 | exactly 1 | ok |
tablist .pui-tabs.pui-tabs--segment |
1 | exactly 1 | ok |
tablist .pui-tabs.pui-tabs--segment.pui-tabs--small |
1 | exactly 1 | ok |
tablist .pui-tabs.pui-tabs--segment |
1 | exactly 1 | ok |
tablist .pui-tabs.pui-tabs--segment.pui-tabs--small.pui-tabs--on-card |
1 | exactly 1 | ok |
What the consumer’s script owes. ONE tab stop: the selected trigger takes tabindex=“0”, every other trigger tabindex=“-1”. The script owes Left / Right to move and select, Home / End for first / last; Tab leaves for the panel.
Tab order across the whole library is document order over 344 stops, and Shift+Tab is its exact reverse — no keyboard trap anywhere (WCAG 2.1.2).
Pepper UI v1.5.0
The Mobupps design system. 386 tokens, 88 components, 65 pages in the workshop — every page of this site generated from the repository, so nothing here is typed twice.