Tabs
Switches between related panels of content in the same place, one visible at a time.
Preview
Submitted on 12 September. A reviewer is checking the treatment summary.
Installation
pnpm add @syntara/react @syntara/tokensimport { Tabs, TabList, Tab, TabPanel } from '@syntara/react';Usage
import { Tab, TabList, TabPanel, Tabs } from '@syntara/react';
export function ClaimTabs() {
return (
<Tabs defaultSelectedKey="overview">
<TabList aria-label="Claim details">
<Tab id="overview">Overview</Tab>
<Tab id="documents" count={3}>Documents</Tab>
</TabList>
<TabPanel id="overview">…</TabPanel>
<TabPanel id="documents">…</TabPanel>
</Tabs>
);
}Examples
Pill
A segmented control for switching views of the same data.
Spending for September, compared with August.
Icons, counts and disabled
Leading icons, a count after the label, and a disabled tab that arrow keys skip.
12 requests are waiting for review.
Vertical
Tabs stacked beside the panel, e.g. for settings.
Your name, photo and contact details.
Accessibility
| Keys | Action |
|---|---|
| Tab | Moves focus into the tab list (to the selected tab), then to the panel. |
| →or← | Moves to the next / previous tab and selects it (mirrored in right-to-left). Wraps around; skips disabled tabs. |
| ↓or↑ | Same as → / ← for vertical tabs. |
| HomeorEnd | Moves to the first / last tab. |
| EnterorSpace | Selects the focused tab when keyboardActivation is manual. |
- role="tablist" / "tab" / "tabpanel" with aria-selected, aria-controls and aria-labelledby wired by React Aria.
- The selected tab is shown by an indicator (underline) or raised surface (pill) plus text colour and weight — not colour alone.
- Overflowing tabs scroll inside the list; the focused tab scrolls into view. Underline tabs draw an inset focus ring because the list clips outside it.
- The panel is focusable when it has no focusable content, so keyboard users can reach it.
Guidelines
Do
- Use short, parallel labels (one or two words).
- Use underline tabs for sections of a page and pill tabs for switching views of the same data.
- Give the TabList an aria-label that says what the tabs switch between.
- With server components, render Tabs, TabList and Tab from a client component ('use client'), or set defaultSelectedKey. Panel contents can still come from the server.
Don’t
- Don't use tabs for steps in a sequence — use Steps.
- Don't use tabs for site navigation between pages unless each Tab has an href.
- Don't nest tabs inside tabs.
- Don't render Tabs straight from a server component without defaultSelectedKey. The server can render no selected tab, and the page then fails hydration.
API reference
Tabs
variantunderlinedefaultpill
underline: a 2px indicator under the selected tab. pill: a segmented control on a sunken track.
orientationhorizontaldefaultvertical
Vertical puts the tab list beside the panel and uses ↑/↓ to move.
selectedKeyKey | nullThe selected tab's id (controlled).
defaultSelectedKeyKeyThe initially selected tab's id (uncontrolled). Defaults to the first enabled tab.
onSelectionChange(key: Key) => voidCalled when the selected tab changes.
keyboardActivationautomaticdefaultmanual
manual: arrow keys move focus only; Enter/Space selects. Use when showing a panel is slow.
disabledKeysIterable<Key>Ids of tabs that can't be selected.
isDisabledbooleanDisables every tab.
Default
false
TabList
aria-labelRequiredstringNames the tab list (or use aria-labelledby).
childrenRequiredReactNode | ((item: T) => ReactElement)Tab elements, or a render function with items.
Tab
idRequiredKeyMatches the TabPanel with the same id.
countReactNodeA small count after the label; part of the tab's accessible name.
isDisabledbooleanMakes the tab unselectable; arrow keys skip it.
Default
falsehrefstringRenders the tab as a link, for tabs that change the URL (pair with a router).
TabPanel
idRequiredKeyMatches the Tab with the same id.
shouldForceMountbooleanKeeps the panel in the DOM while another tab is selected (inert and display: none), so its state survives switching.
Default
false
Tokens
The semantic tokens this component reads, grouped by what they control. Swatches show this site’s theme; change a tenant’s brand and the component follows with no code change.
- Colour8
border.subtlesurface.sunkentext.subtletext.defaulttext.disabledfocus.ringaction.primary.bgsurface.raised- Type4
font.size.mdfont.weight.mediumline-height.tightfont.size.xs- Space and size7
space.4space.6space.1space.2space.3control-heightspace.5- Shape3
radius.buttonradius.badgeradius.container- Depth2
shadow.highlightshadow.raised- Motion6
motion.duration.fastmotion.easingmotion.duration.springmotion.springmotion.duration.normalmotion.easing-out