Tabs
Switches between panels of one view.
Examples
42 messages across 3 agents.
Unsaved changes
Styles
defaultdefault
line
- Unsaved changes
document
Sizes
defaultdefault
sm
States
rest
hover
focus-visible
disabled
Usage
When to use it
- Use
- One view has two to seven panels of the same object, read one at a time: thread, activity, settings.
- Use
- People keep several things open and switch between them: variant="document".
- Not for
- The choice changes how one set of data is shown, like a chart range. Use Segmented control.
- Not for
- One section shows or hides under a heading. Use Collapsible.
- Not for
- The person goes through the panels in order. Use Progress.
Rules
9 rules
- Must
- Give every TabsList aria-label naming the object: 'Conversation'. Because a screen reader announces an unnamed "tab list", so two tab sets on one page sound the same.
- Must
- Keep every tab about the same object at the same level, two to seven tabs. Because a tab bar reads as one object seen several ways, so Billing beside Activity hides a page people look for in navigation.
- Must
- Pass keepMounted to a TabsContent that holds typed or scrolled state. Because Base UI unmounts a hidden panel, so switching away and back drops the typed text and the scroll position.
- Must
- Select the next tab, or the previous when it was last, when a document tab closes. Because closing the selected tab leaves none selected, so the panel goes blank and focus drops to the page.
- Must
- Pass label with the full name on a pinned (icon-only) document tab. Because a pinned tab hides its text, so without label a screen reader reads the value: "run_8f2a, pinned".
- Should
- Label with a noun of one or two words under 20 characters and a TabsCount for how many.
- Never
- Mix pill, underline and per-tab icons; pick default or line and keep icons off. Because an underline on one tab and a chip on another both read as selected, so the person cannot tell which panel shows.
- Should
- Let a document tab label truncate at 220px and carry the full name in label.
- Must
- Render tabs only with at least two triggers. Because a single tab switches nothing, and a screen reader still announces a tab list of one.
Accessibility
8 checks
- The list is a tablist named by aria-label; each trigger is a tab with aria-selected and aria-controls.
- Tab focuses the selected tab; a second Tab goes to the panel.
- Arrow keys move and select on default and line, skipping disabled and mirroring in RTL.
- Document tabs use manual activation: arrows move focus only; Enter or Space selects.
- Home and End jump to the first and last tab.
- Delete or Backspace closes a closable document tab, named by aria-keyshortcuts. The chip's cross is aria-hidden, so the tablist holds only tabs; the selected tab's named Close button sits beside the list, for touch screen readers and voice control.
- Alt+Left and Alt+Right reorder a document tab within its group when onReorder is set.
- A dirty tab carries sr-only "unsaved changes".
Edge cases
11 edge cases
- Long label at 320px
- Tabs never wrap; the list scrolls with a fade. Document tabs cap at 220px.
- 25 tabs at 160px
- One scroller with the end fade; the selected tab scrolls into view.
- Right to left
- Tabs mirror, arrows mirror, and the fade flips.
- Zoom 200%
- Tabs take min-height and grow; the list scrolls.
- Kind on a document tab
- Kept as data-kind but not drawn; the panel header writes the kind.
- One-letter tab
- Keeps 12px side padding.
- Closing a dirty tab
- onClose confirms first: Delete and Backspace close in one keystroke, and Backspace is easy to hit.
- No triggers or no value
- An empty row with its line renders and no panel shows.
- Panel loading or failing
- The panel owns Skeleton or Empty state; the tab switches at once.
- Forced colors
- The selected tab gets a 1px outline; the line paints Highlight.
- Reduced motion
- The line indicator jumps.
API
10 props
Tabs value, defaultValue, onValueChange, orientationBase UI Tabs.Rootdefault'horizontal'TabsList variant'default' | 'line' | 'document'default'default'TabsList size'default' | 'sm'default'default'TabsList onClose(value) => voidTabsList onReorder(values: string[]) => voidTabsList activateOnFocusbooleandefaulttrue; false for documentTabsList actionsReactNodeTabsTrigger kind, pinned, dirty, label, iconKind, boolean, boolean, string, ReactNodeTabsContent value, keepMountedBase UI Tabs.PanelTabsCountspan
Tokens
19 tokens
--line-frame- oklch(var(--ink-c) / var(--line-frame-alpha))
--line-row- oklch(var(--ink-c) / 0.08)
--radius-soft- 6px
--radius-inner- 4px
--radius-frame- 10px
--control- 32px
--control-sm- 28px
--space-6- 24px
--text-2- oklch(var(--ink-c) / 0.80)
--text-1- var(--ink)
--surface-hover- oklch(var(--ink-c) / 0.05)
--surface-page- var(--paper)
--surface-selected- oklch(var(--ink-c) / 0.09)
--text-disabled- oklch(var(--ink-c) / 0.40)
--line-accent- 2px
--dur-fast- 120ms
--ease-step- cubic-bezier(0.2, 0, 0, 1)
--ink- oklch(var(--ink-c))
--tab-max- 220px