Tabs
In-page tab list and panels with a moving indicator and scrollable overflow.
Base UI
Tabs keep related views on one screen. Compose a Tabs.List of Tabs.Tab triggers with matching Tabs.Panel children inside Tabs.Viewport. The root owns selection — defaultValue when the compound owns it, or value plus onValueChange when the URL, a deep link, or another control drives the active tab.
Each tab’s value pairs with exactly one panel of the same value. Only the selected panel stays mounted while the indicator moves under the active trigger. Long label sets scroll horizontally inside the list; that overflow is expected, not a layout bug.
listAlignment aligns the segment chrome through the same stack contract as Stack — prefer center for a compact list; use stretch when equal-width tabs should fill the row. Root size scales tab items and the indicator with the same s / m / l steps as Button (default m). Set activateOnFocus={false} on Tabs.List when keyboard focus should move without changing selection until click or Enter. Labels accept addon slots for icons or Chip badges; disabled tabs ignore activation.
Anatomy
Import the component and assemble its parts:
import { Tabs } from '@viraui/react';
<Tabs>
<Tabs.List>
<Tabs.Tab />
</Tabs.List>
<Tabs.Viewport>
<Tabs.Panel />
</Tabs.Viewport>
</Tabs>When to use
- Peer sections of the same screen (profile, billing, goals)
- In-page wayfinding where content swaps without a route change
- Selection that should sync to search params, a deep link, or another control
When not to use
- Expand/collapse FAQs or stacked disclosures → Accordion
- Ordered multi-step flows (continue / next / back) → a dedicated stepper, not Tabs
- Primary app routes or top-level destinations → links and the router
- Commands from a trigger → Menu
Related
- Accordion — expand/collapse sections on the same page
- Menu — command lists anchored to a trigger
- Chip — badges in tab addons
- Stack —
listAlignmentshares the horizontal align contract
Common examples
Tabbed panels
A three-tab block with paired list triggers and viewport panels is the default in-page navigation pattern. Seed the active tab with defaultValue, and keep the list compact with listAlignment="center".
Size
size on the root steps tab chrome and the active indicator with the same scale as Button. Default is medium; reach for s in tight preference strips.
Tab addons
Put icons or Chip badges in addon without changing the accessible name — visible tab text stays the label. Use addonPosition="end" for trailing badges.
Stretch list
listAlignment="stretch" fills the list chrome so equal-width tabs sit edge to edge — useful for two-option choosers.
Scrollable overflow
When labels exceed the list width, the tab row scrolls horizontally. Constrain the shell so overflow is visible; do not force wrap or shrink labels to fit.
Programmatic selection
Drive selection with value and onValueChange when a panel action, URL param, or deep link owns the active tab. Call the same setter from a Button inside a panel to jump to a peer section — for example Profile → Billing — not as a step sequence.
Set activateOnFocus={false} on Tabs.List when arrow-key focus should not change the selected panel until the user activates with click, Enter, or Space.
Ask your agent
Add centered settings tabs — General, Billing, Goals — each with a short title and muted description. Use listAlignment center. Sync tab selection to URL search params if the screen already uses them.Last updated on