Skip to content

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/tokens
import { 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

KeysAction
TabMoves 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.
HomeorEndMoves to the first / last tab.
EnterorSpaceSelects 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

variant
  • underlinedefault
  • pill

underline: a 2px indicator under the selected tab. pill: a segmented control on a sunken track.

orientation
  • horizontaldefault
  • vertical

Vertical puts the tab list beside the panel and uses ↑/↓ to move.

selectedKey
Key | null

The selected tab's id (controlled).

defaultSelectedKey
Key

The initially selected tab's id (uncontrolled). Defaults to the first enabled tab.

onSelectionChange
(key: Key) => void

Called when the selected tab changes.

keyboardActivation
  • automaticdefault
  • manual

manual: arrow keys move focus only; Enter/Space selects. Use when showing a panel is slow.

disabledKeys
Iterable<Key>

Ids of tabs that can't be selected.

isDisabled
boolean

Disables every tab.

Default false

TabList

aria-labelRequired
string

Names the tab list (or use aria-labelledby).

childrenRequired
ReactNode | ((item: T) => ReactElement)

Tab elements, or a render function with items.

Tab

idRequired
Key

Matches the TabPanel with the same id.

count
ReactNode

A small count after the label; part of the tab's accessible name.

isDisabled
boolean

Makes the tab unselectable; arrow keys skip it.

Default false

href
string

Renders the tab as a link, for tabs that change the URL (pair with a router).

TabPanel

idRequired
Key

Matches the Tab with the same id.

shouldForceMount
boolean

Keeps 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