Persona

Tab Bar

Navigation · 5 props · Updated Sep 22, 2026

Tabs switch between sibling views without leaving the page. Tab Bar is the bar and the tabs only: it draws them, marks one as current, and moves focus between them with the arrow keys. The panel underneath is yours.

  • Reports
    Reports
    Tab bar the bar itself, holding the title, the tabs and the actions slot.
  • Reports
    Reports
    Title (optional) names the set of tabs. It sits inside the bar, before the first tab.
  • Reports
    Reports
    Active tab the view you are looking at, marked by the underline and nothing else — so nothing else in the bar should read as current.
  • Reports
    Reports
    Inactive tab a sibling view, one click away.
  • Reports
    Reports
    Badge (optional) a count on a tab. badge and sticker are exclusive; a tab takes one or neither.
  • Reports
    Reports
    Actions (optional) the slot at the end of the bar. actions takes any node — an add-tab button here, an overflow menu elsewhere.

Use tabs for two to six views of the same object, where the reader will go back and forth between them and nothing is lost by hiding one. An account’s Overview, Verifications and Devices are tabs; the steps of a form are not, because a step has to be finished before the next one means anything.

Use Link for a destination that replaces the page rather than a region of it, and a Dropdown Menu for actions on the object the tabs belong to. A tab whose panel repeats another tab’s panel with one value changed is a filter, not a view, and belongs in the panel as a Button Group.

Tabs can drive the URL: as renders a tab as a link or a router link instead of a button, and the styles for that are in the component. What makes it a tab is still that the destination renders in the region below the bar.

selected is a prop on each tab, and nothing above them coordinates it. The component does not clear the previous tab’s underline or hold a value of its own, so two tabs passed selected both draw the underline and both report aria-selected. The current view lives in your state, and the bar renders it.

The underline is the entire signal. Nothing else in the bar changes with selection, which is why nothing else in the bar should look emphasized.

A tab that does not fit is cut off, not moved

Section titled “A tab that does not fit is cut off, not moved”

There is no overflow control: no scroller, no overflow menu, no affordance for a tab you cannot see. The tab row wraps, and the bar is a fixed 32px high with hidden overflow, so a tab that does not fit is cut off rather than moved somewhere reachable — present in the DOM, not on the screen. Decide the number of tabs against the narrowest place the bar will render, not against your own window.

Truncate by character count, or not at all

Section titled “Truncate by character count, or not at all”

maxTabChars cuts each label to that many characters and appends an ellipsis. It is a character count rather than a width, so it truncates identically in a wide bar and a narrow one, and it only applies to a tab whose child is a plain string — a tab holding markup is left at full length. Setting it also puts the untruncated text in a title on every string tab, so hovering any tab in the bar produces a browser tooltip, including the ones that were short enough.

The bar renders role="tablist" and each tab role="tab" with aria-selected. The third role is not here: there is no panel component, so role="tabpanel", its aria-labelledby, and the aria-controls on the tab pointing back at it are yours to write. Two of the three roles agreeing is not the pattern — a tab with no aria-controls announces itself as a tab belonging to nothing.

The arrow keys move focus along the bar and wrap at both ends, and Home and End jump to the first and last; disabled tabs are skipped. No tab is removed from the tab order, though, so Tab walks through all of them one at a time rather than leaving the bar. Moving focus does not switch view — a tab activates on Enter, Space or click.

Move the bar’s heading outside the tablist

Section titled “Move the bar’s heading outside the tablist”

title renders inside the tablist, before the first tab, as a div with no role. The arrow keys count it as one of the stops, so with a title set, Left from the first tab and Right from the last land on it and focus stays where it was instead of wrapping. Name the bar with aria-label and put a visible heading above it.

A tab label is the name of what the panel shows: “Verifications”, “Devices”, “Activity”. Tabs are read against each other, so the label only has to distinguish this view from its siblings, and a shared word in all of them — “Account details”, “Account devices” — is width spent on something the reader already knows.

A tab carries a count or a label beside its text, never both: badge and sticker are mutually exclusive in the type. A Badge is recolored to match the tab’s selected state, so a count reads as part of the tab; a Sticker is rendered exactly as you pass it, which is what makes it the one to use for “Beta” and other labels that should not change with selection.

NameType
actions

ReactNode

title

string

data-tour-target

string

maxTabChars

number

Truncate tab text to the specified number of characters.

size

md, sm

Size of the tabs. Affects font size.

Also accepts every prop of ComponentPropsWithoutRef<‘div’>.

Was this page helpful?