Thumbprint logo

Components

Tabs

Switch between related sections of content within the same context

About

Tabs is built on React Aria and styled with Thumbprint v2 semantic tokens, so it automatically supports light and dark mode. It is a compositional component: render a single TabList containing one Tab per section, followed by a TabPanels containing one TabPanel per tab. Each Tab is paired with its TabPanel by a matching id. TabList can alternatively render its tabs from an array via the tabs prop.

Basic tabs

Selecting a tab reveals its associated panel. When selectedKey is omitted, the component is uncontrolled: React Aria manages the selection state internally, selecting the first tab (or defaultSelectedKey) initially and updating the selection as the user interacts — no state management is required.

<Tabs>
    <TabList accessibilityLabel="Account sections">
        <Tab id="overview">Overview</Tab>
        <Tab id="reviews">Reviews</Tab>
        <Tab id="photos">Photos</Tab>
    </TabList>
    <TabPanels>
        <TabPanel id="overview">An overview of the pro.</TabPanel>
        <TabPanel id="reviews">What customers are saying.</TabPanel>
        <TabPanel id="photos">A gallery of past work.</TabPanel>
    </TabPanels>
</Tabs>

Controlled selection

Provide selectedKey and onSelectionChange to control which tab is selected. The onSelectionChange callback receives the id of the newly selected tab.

function TabsExample() {
    const [selectedKey, setSelectedKey] = React.useState('overview');
    return (
        <Tabs selectedKey={selectedKey} onSelectionChange={setSelectedKey}>
            <TabList accessibilityLabel="Account sections">
                <Tab id="overview">Overview</Tab>
                <Tab id="reviews">Reviews</Tab>
                <Tab id="photos">Photos</Tab>
            </TabList>
            <TabPanels>
                <TabPanel id="overview">An overview of the pro.</TabPanel>
                <TabPanel id="reviews">What customers are saying.</TabPanel>
                <TabPanel id="photos">A gallery of past work.</TabPanel>
            </TabPanels>
        </Tabs>
    );
}

Tabs from an array

As an alternative to composing Tab children, pass TabList an array of tab props via the tabs prop. Provide either children or tabs, not both.

<Tabs>
    <TabList
        accessibilityLabel="Account sections"
        tabs={[
            { id: 'overview', children: 'Overview' },
            { id: 'reviews', children: 'Reviews', count: 12 },
            { id: 'photos', children: 'Photos' },
        ]}
    />
    <TabPanels>
        <TabPanel id="overview">An overview of the pro.</TabPanel>
        <TabPanel id="reviews">What customers are saying.</TabPanel>
        <TabPanel id="photos">A gallery of past work.</TabPanel>
    </TabPanels>
</Tabs>

Tabs with icons

Pass an icon to a Tab. By default the icon is stacked above the label (iconPlacement="stacked"); set iconPlacement="inline" to place it beside the label instead.

<Tabs>
    <TabList accessibilityLabel="Account sections">
        <Tab id="overview" icon={<ContentModifierListSmall />}>
            Overview
        </Tab>
        <Tab id="reviews" icon={<InputsStarSmall />}>
            Reviews
        </Tab>
    </TabList>
    <TabPanels>
        <TabPanel id="overview">An overview of the pro.</TabPanel>
        <TabPanel id="reviews">What customers are saying.</TabPanel>
    </TabPanels>
</Tabs>

Count pill

The count prop renders a neutral pill after the label, typically a count of the items in that section.

<Tabs>
    <TabList accessibilityLabel="Inbox sections">
        <Tab id="all" count={128}>All</Tab>
        <Tab id="unread" count={12}>Unread</Tab>
        <Tab id="archived" count={0}>Archived</Tab>
    </TabList>
    <TabPanels>
        <TabPanel id="all">All messages.</TabPanel>
        <TabPanel id="unread">Unread messages.</TabPanel>
        <TabPanel id="archived">Archived messages.</TabPanel>
    </TabPanels>
</Tabs>

Alert badge

The badge prop renders a small alert badge after the label, drawing attention to a section that needs the user’s attention.

<Tabs>
    <TabList accessibilityLabel="Inbox sections">
        <Tab id="messages" badge={3}>Messages</Tab>
        <Tab id="alerts" badge={88}>Alerts</Tab>
        <Tab id="settings">Settings</Tab>
    </TabList>
    <TabPanels>
        <TabPanel id="messages">Your messages.</TabPanel>
        <TabPanel id="alerts">Your alerts.</TabPanel>
        <TabPanel id="settings">Your settings.</TabPanel>
    </TabPanels>
</Tabs>

Divider

Set showBorder on the TabList to render a full-width divider beneath the tab strip. The selected tab’s indicator sits on top of the divider.

<Tabs>
    <TabList accessibilityLabel="Account sections" showBorder>
        <Tab id="overview">Overview</Tab>
        <Tab id="reviews">Reviews</Tab>
        <Tab id="photos">Photos</Tab>
    </TabList>
    <TabPanels>
        <TabPanel id="overview">An overview of the pro.</TabPanel>
        <TabPanel id="reviews">What customers are saying.</TabPanel>
        <TabPanel id="photos">A gallery of past work.</TabPanel>
    </TabPanels>
</Tabs>

Overflow

When there are more tabs than fit the container — common on small screens and mobile web — the tab strip scrolls horizontally. The scrollbar is hidden: users scroll by touch or trackpad, selecting a partially visible tab brings it fully into view, and keyboard focus automatically scrolls off-screen tabs into view. No extra props are needed. Resize the browser to watch the strip below overflow and scroll.

<Tabs>
    <TabList accessibilityLabel="Profile sections" showBorder>
        <Tab id="overview">Overview</Tab>
        <Tab id="reviews" count={128}>Reviews</Tab>
        <Tab id="photos">Photos</Tab>
        <Tab id="services">Services offered</Tab>
        <Tab id="credentials">Credentials</Tab>
        <Tab id="availability">Availability</Tab>
        <Tab id="pricing">Pricing</Tab>
        <Tab id="faq">Frequently asked questions</Tab>
        <Tab id="similar">Similar pros</Tab>
        <Tab id="service-area">Service area</Tab>
        <Tab id="promotions" count={2}>Promotions</Tab>
        <Tab id="contact">Contact</Tab>
    </TabList>
    <TabPanels>
        <TabPanel id="overview">An overview of the pro.</TabPanel>
        <TabPanel id="reviews">What customers are saying.</TabPanel>
        <TabPanel id="photos">A gallery of past work.</TabPanel>
        <TabPanel id="services">Services this pro offers.</TabPanel>
        <TabPanel id="credentials">Licenses and certifications.</TabPanel>
        <TabPanel id="availability">When this pro is available.</TabPanel>
        <TabPanel id="pricing">Typical project pricing.</TabPanel>
        <TabPanel id="faq">Answers to common questions.</TabPanel>
        <TabPanel id="similar">Other pros like this one.</TabPanel>
        <TabPanel id="service-area">Where this pro works.</TabPanel>
        <TabPanel id="promotions">Current deals and offers.</TabPanel>
        <TabPanel id="contact">How to reach this pro.</TabPanel>
    </TabPanels>
</Tabs>

Responsive tabs

To show or hide tabs at certain viewport sizes, conditionally render the Tab and its TabPanel — for example with the useBreakpoint hook from @lib/hooks, as below. Avoid hiding a tab with CSS (display: none): the tab stays in the collection, so arrow-key navigation tries to move focus to the hidden element and breaks. The isSmall === false check matters because isSmall is undefined until hydration completes. Resize the browser below the small breakpoint (480px) to watch the Photos tab drop out of this live example.

function ResponsiveTabs() {
    const { isSmall } = useBreakpoint();
    return (
        <Tabs>
            <TabList accessibilityLabel="Account sections">
                <Tab id="overview">Overview</Tab>
                <Tab id="reviews">Reviews</Tab>
                {isSmall === false ? <Tab id="photos">Photos</Tab> : null}
            </TabList>
            <TabPanels>
                <TabPanel id="overview">An overview of the pro.</TabPanel>
                <TabPanel id="reviews">What customers are saying.</TabPanel>
                {isSmall === false ? (
                    <TabPanel id="photos">A gallery of past work.</TabPanel>
                ) : null}
            </TabPanels>
        </Tabs>
    );
}

Props

Tabs

  • children
    required

    The Tabs and TabPanels to render. Compose a single TabList followed by a TabPanels containing one TabPanel per tab.

    Type
    React.ReactNode
  • selectedKey

    The currently selected tab, matching the id of a Tab. Use together with onSelectionChange for a controlled component. When omitted, the component is uncontrolled: React Aria manages the selection state internally, starting at defaultSelectedKey (or the first tab) and updating it as the user selects tabs — no onSelectionChange handler or state management is required. Maps to React Aria's selectedKey.

    Type
    string | number
  • defaultSelectedKey

    The tab selected initially when the component is uncontrolled (no selectedKey), matching the id of a Tab. Has no effect when selectedKey is provided. Maps to React Aria's defaultSelectedKey.

    Type
    string | number
  • onSelectionChange

    Function that runs when the selected tab changes. It receives the id of the newly selected tab. Maps to React Aria's onSelectionChange.

    Type
    (key: string | number) => void
  • keyboardActivation

    Whether tabs are activated automatically on focus ('automatic', the default) or only when selected by the user ('manual'). Maps to React Aria's keyboardActivation.

    Type
    'automatic' | 'manual'
  • id

    The id is added to the underlying element as an HTML attribute.

    Type
    string
  • dataTestId

    A selector hook into the React component for use in automated testing environments.

    Type
    string

Tab

  • id
    required

    Uniquely identifies the tab and pairs it with its TabPanel. Maps to React Aria's id.

    Type
    string | number
  • children

    Text or elements that appear within the tab label.

    Type
    React.ReactNode
  • icon

    An optional icon rendered above the label (iconPlacement="stacked", the default) or beside it (iconPlacement="inline").

    Type
    React.ReactNode
  • iconPlacement

    Controls whether the icon sits above the label ('stacked', the default) or beside it ('inline'). Maps to the [data-icon-placement] attribute.

    Type
    'inline' | 'stacked'
    Default
    'stacked'
  • count

    Optional content rendered in a neutral pill after the label, typically a count.

    Type
    React.ReactNode
  • badge

    Optional content rendered in an alert badge after the label, typically a notification count.

    Type
    React.ReactNode
  • href

    Renders the tab as a link to the given URL. Maps to React Aria's href.

    Type
    string
  • dataTestId

    A selector hook into the React component for use in automated testing environments.

    Type
    string

TabList

  • children

    The Tabs to render within the list. Provide either children or tabs, not both.

    Type
    React.ReactNode
  • tabs

    Renders the list from an array of TabProps as an alternative to composing Tab children. Provide either children or tabs, not both.

    Type
    TabProps[]
  • accessibilityLabel

    Accessible label for the tab list. Required for assistive technologies to describe the set of tabs. Maps to React Aria's aria-label.

    Type
    string
  • showBorder

    Renders a full-width divider beneath the tab strip. Maps to Figma's showBorder and the [data-show-border] attribute.

    Type
    boolean
    Default
    false
  • dataTestId

    A selector hook into the React component for use in automated testing environments.

    Type
    string

TabPanels

  • children
    required

    The TabPanels to render, one per Tab.

    Type
    React.ReactNode
  • dataTestId

    A selector hook into the React component for use in automated testing environments.

    Type
    string

TabPanel

  • id
    required

    Matches the id of the Tab this panel belongs to. Maps to React Aria's id.

    Type
    string | number
  • children

    The content shown when the associated tab is selected.

    Type
    React.ReactNode
  • dataTestId

    A selector hook into the React component for use in automated testing environments.

    Type
    string