Switch between panels of content, wrapping the Base UI Tabs. The active background slides between tabs with a light spring.

Playground

Every control maps to a prop. The Code tab always shows the markup for exactly what you've configured.

Name, avatar, and how you appear.

Variants

Three flavors, one component: text, text with icons, and icon only. Icons are just children, so there's no separate API to learn.

States

Active, inactive, and disabled. Keyboard focus adds a ring on top of whichever state a tab is in.

Styling states

The active tab carries data-active; the indicator position comes through Base UI's custom properties, so restyling never needs JavaScript:

.ub-tabs-tab[data-active] {
  color: var(--ub-accent);
}

Props

Each part passes the full Base UI API through. The essentials:

PropTypeDefaultDescription
TabsRoot.defaultValueanyThe tab selected on first render, when uncontrolled.
TabsRoot.valueanyControlled selection. Pair with onValueChange.
TabsRoot.onValueChange(value) => voidCalled when the active tab changes.
TabsRoot.size"default" | "compact""default"Default is 36px tall tabs with 16px text; compact is 28px with 14px text.
TabsTab.valueanyIdentity of the tab; matches a Panel's value.
TabsTab.iconOnlybooleanfalseSquare tab holding only an icon; pair with aria-label.
TabsTab.disabledbooleanfalseBlocks selecting this tab.
TabsPanel.valueanyShown while the matching Tab is active.

Agent instructions

Working with a coding agent? Paste this into its context, or point it at llms.txt for the whole library.

TabsRoot, TabsList, TabsTab, TabsPanel, from @usebones/react.
- TabsRoot: defaultValue or value + onValueChange; size "default" | "compact".
- TabsList renders the sliding indicator automatically. Each TabsTab value matches a TabsPanel value.
- Icons are children via @usebones/icons; iconOnly makes a square tab and requires aria-label. disabled blocks a tab.
- Restyle in CSS via [data-active], [data-disabled]; the indicator is .ub-tabs-indicator. Tokens only.