Skip to main content

Tabs

Implemented

Horizontal tab group with underline indicator. WAI-ARIA tabs pattern: role='tablist', roving tabindex, ArrowLeft/Right to navigate. variant='href' renders the former TabsTrigger link-mode strip (anchor or button tabs, underline or pills appearance) — TabsTrigger was merged into Tabs (Wave 3 Tier-1, L3-M3).

Categorymolecules
Sourcepatterns/molecules/tabs.html
AI HintUse for grouped UI patterns. Combine atoms into functional units.

Tab strip with two rendering modes: state (default — WAI-ARIA tabs pattern with an inline tabpanel) and href (link-driven strip, the former TabsTrigger molecule).

Tabs

molecules

State mode (default): button-driven tabs with roving tabindex, ArrowLeft/ArrowRight/Home/End navigation, focus-follows-selection, and an inline tabpanel for the active item; keyboard navigation skips disabled tabs. Href mode (link-driven, no keyboard nav): items carrying an href render anchors, items without one render buttons that call onChange — there are no arrow keys, no roving tabindex, and no tabpanel in this mode. The appearance prop selects the visual treatment in both variants: "underline" (tab bar with a bottom rule) or "pills" (prominent filled chips).

state mode (default) — button tabs + tabpanel

Line-up, venue rules, and accessibility info for the show.

state mode + appearance="pills" — prominent treatment, same keyboard semantics

Line-up, venue rules, and accessibility info for the show.

href mode (link-driven, no keyboard nav) — no tabpanel

appearance="underline" (default)

appearance="pills"

“Overview” and “Schedule” are anchors (href items) — clicking them navigates. “Merch” has no href, so it renders a button and calls onChange. “Archive” is disabled.

Usage

Tabs is a controlled component — pair activeId with local state and wire onChange to it. Each item's panel renders inside the active tabpanel. Keyboard navigation (ArrowLeft/ArrowRight, Home/End) and roving tabindex are built in for state mode only — the default variant="state".

variant="href" is link-driven — no arrows, no tabpanel, no roving tabindex. Each href item is a plain anchor in the normal tab order; each href-less item is a button that calls onChange. Use it for URL-driven sub-navigation, not for swapping in-page content panels.

import { Tabs } from '@epasslive/design-system'

// state mode (default) — controlled, with an inline tabpanel
const [activeId, setActiveId] = useState('overview')

<Tabs
  ariaLabel="Event information"
  activeId={activeId}
  onChange={setActiveId}
  items={[
    { id: 'overview', label: 'Overview', panel: <p>Line-up and venue info.</p> },
    { id: 'tickets', label: 'Tickets', badge: 3, panel: <p>3 tiers on sale.</p> },
  ]}
/>

// href mode — link-driven strip (former TabsTrigger), no tabpanel
<Tabs
  variant="href"
  appearance="pills"
  ariaLabel="Event sections"
  activeId={activeId}
  onChange={setActiveId}
  items={[
    { id: 'overview', label: 'Overview', href: '/events/42' },
    { id: 'schedule', label: 'Schedule', href: '/events/42/schedule' },
    { id: 'merch', label: 'Merch' }, // no href → button, calls onChange
  ]}
/>

Props

Prop
Type
Default
Description
items*
TabItem[]
—

Tab items — id, label, optional badge/panel/href/disabled.

activeId*
string
—

Id of the active tab.

onChange*
(id: string) => void
—

Called when a tab is activated (button items only in href mode).

ariaLabel*
string
—

Accessible name for the tablist.

variant
"state" | "href"
"state"

Button-driven tabs vs link-driven strip.

appearance
"underline" | "pills"
"underline"

Visual treatment (both variants): underline tab-bar, or prominent filled pills.

className
string
—

Additional CSS classes.

id
string
—

Stable id prefix for tabs and panels.

Props

Preview

Tabs

Prop
Type
Required
Default
Description
items
TabItem[]
Required
—

2-6 tabs

activeId
string
Required
—
No description.
onChange
(id: string) => void
Required
—
No description.
variant
'state' | 'href'
Optional
'state'

state: button tabs with keyboard nav + inline tabpanel; href: link-mode strip (former TabsTrigger)

appearance
'underline' | 'pills'
Optional
'underline'

visual treatment (both variants): underline tab-bar, or prominent filled pills

ariaLabel
string
Required
—

Synced from source — no interface JSDoc yet; document on `Tabs.tsx`.

Quick use:<Tabs />

Accessibility

ARIA roletablist

Type

1 rule

NASA Rule 2

items bounded 2-6 (NASA Rule 2)

ARIA

1 rule

WAI-ARIA Tabs Pattern

role='tablist' aria-label required; each tab role='tab' with aria-selected + aria-controls

Usage

Import

tsxSnippet
import { Tabs } from "@epasslive/design-system"

Example 1

tsxSnippet
<Tabs />

Example 2

tsxSnippet
<Tabs variant="default" />

Anti-patterns

— Do not hardcode data in molecules

— Avoid complex business logic in molecules

Design System v4.1.0 | ePassLive