Skip to content

Tabs

A compound tab component for organising content into switchable panels. Supports three visual variants and full keyboard navigation.

Preview

Underline variant

Security settings content goes here.

Pills variant

Active items list.

Enclosed variant

Analytics dashboard content.

Variants

  • Underline (default) -- a 2 px purple indicator bar below the active tab. Clean and minimal.
  • Pills -- the active tab gets a muted purple background with rounded corners.
  • Enclosed -- tabs sit inside a bordered container; the active tab has a raised surface.

States

StateAppearance
DefaultMuted text colour
HoverText shifts toward primary colour
ActiveFull contrast text, variant-specific indicator
Disabled50% opacity, cursor: not-allowed
Focus2 px brand-purple ring

Compound pattern

PartDescription
TabsRoot provider. Manages active state.
Tabs.ListHorizontal bar containing the tab triggers.
Tabs.TabIndividual tab trigger.
Tabs.PanelContent panel linked to a tab.

Props

Tabs

PropTypeDefaultDescription
defaultValuestring--Initially active tab value
valuestring--Controlled active tab value
onChange(value: string) => void--Called when the active tab changes
variant'underline' | 'pills' | 'enclosed''underline'Visual style
classNamestring--Additional CSS classes

Tabs.Tab

PropTypeDefaultDescription
valuestring--Unique identifier matching a panel
disabledbooleanfalseDisables this tab
childrenReact.ReactNode--Tab label

Tabs.Panel

PropTypeDefaultDescription
valuestring--Matches a Tab value
childrenReact.ReactNode--Panel content

Code example

React

tsx
import { Tabs } from '@thepace/lexicon/components';

function SettingsPage() {
  return (
    <Tabs defaultValue="general" variant="underline">
      <Tabs.List>
        <Tabs.Tab value="general">General</Tabs.Tab>
        <Tabs.Tab value="security">Security</Tabs.Tab>
        <Tabs.Tab value="billing">Billing</Tabs.Tab>
        <Tabs.Tab value="api" disabled>API</Tabs.Tab>
      </Tabs.List>

      <Tabs.Panel value="general">
        <p>General settings content.</p>
      </Tabs.Panel>
      <Tabs.Panel value="security">
        <p>Security settings content.</p>
      </Tabs.Panel>
    </Tabs>
  );
}

Vanilla HTML

html
<div class="lex-tabs lex-tabs--underline">
  <div class="lex-tabs__list" role="tablist">
    <button class="lex-tabs__tab lex-tabs__tab--active" role="tab"
            aria-selected="true">General</button>
    <button class="lex-tabs__tab" role="tab"
            aria-selected="false">Security</button>
  </div>
  <div class="lex-tabs__panel" role="tabpanel">
    <p>General settings content.</p>
  </div>
</div>

CSS class reference

ClassPurpose
.lex-tabsRoot container
.lex-tabs--underlineUnderline variant
.lex-tabs--pillsPills variant
.lex-tabs--enclosedEnclosed variant
.lex-tabs__listTab trigger bar
.lex-tabs__tabIndividual tab trigger
.lex-tabs__tab--activeActive tab
.lex-tabs__panelContent panel

Accessibility

  • Tab triggers use role="tab" with aria-selected state.
  • The tab list uses role="tablist".
  • Panels use role="tabpanel" linked to their trigger via aria-labelledby.
  • Arrow keys move focus between tabs. Home and End jump to the first and last tab.
  • Only the active tab is in the tab order (tabindex="0"); inactive tabs have tabindex="-1".
  • Disabled tabs are skipped during keyboard navigation.

Guidelines

Do

  • Use underline for page-level navigation (settings, profile sections).
  • Use pills for filtering or toggling content views.
  • Use enclosed for dashboard widget panels.

Don't

  • Don't use more than 6 tabs — consider a dropdown or sidebar instead.
  • Don't nest tabs within tabs.

Released under the MIT License. A product by the pace.