Skip to main contentSkip to docs navigation

Chassis Tabs organize content into multiple sections and let users navigate between them with full keyboard support.

Overview

Tabs organizes content into sections, showing one at a time. Unlike hand-wiring a Nav to a set of manually-toggled panels, Tabs handles keyboard navigation (arrow keys, Home/End), roving tab focus, and the role="tablist"/role="tab"/role="tabpanel" + aria-selected/aria-controls/aria-labelledby wiring between them. Switching tabs also cross-fades their panels automatically.

Compose a TabList of Tabs, followed by one TabPanel per tab. A tab and its panel are paired by a matching id — not by position, and not by React's own key (each still needs its own key, as with any list). Tabs/TabList/Tab/TabPanel mirror the names of the react-aria hooks they're built on (useTabList, useTab, useTabPanel).

Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse.
JSX
import { Tabs, TabList, Tab, TabPanel } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Tabs defaultSelectedKey="home">
      <TabList aria-label="Example tabs">
        <Tab id="home">Home</Tab>
        <Tab id="profile">Profile</Tab>
        <Tab id="contact">Contact</Tab>
      </TabList>
      <TabPanel id="home">
        Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown
        aliqua, retro synth master cleanse.
      </TabPanel>
      <TabPanel id="profile">
        Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid.
        Exercitation +1 labore velit, blog sartorial PBR leggings.
      </TabPanel>
      <TabPanel id="contact">
        Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic
        lomo retro fanny pack lo-fi farm-to-table readymade.
      </TabPanel>
    </Tabs>
  )
}

Variants and sizes

Pass variant="segments" to TabList for a segmented control instead of the default tabs. The variant was called pills before @chassis-ui/css 0.6: variant="pills" still renders it, and is deprecated.

Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse.
JSX
import { Tabs, TabList, Tab, TabPanel } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Tabs defaultSelectedKey="home">
      <TabList aria-label="Segments example" variant="segments">
        <Tab id="home">Home</Tab>
        <Tab id="profile">Profile</Tab>
        <Tab id="contact">Contact</Tab>
      </TabList>
      <TabPanel id="home">
        Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown
        aliqua, retro synth master cleanse.
      </TabPanel>
      <TabPanel id="profile">
        Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid.
        Exercitation +1 labore velit, blog sartorial PBR leggings.
      </TabPanel>
      <TabPanel id="contact">
        Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic
        lomo retro fanny pack lo-fi farm-to-table readymade.
      </TabPanel>
    </Tabs>
  )
}

Pass variant="underline" to mark the selected tab with a line under it, and size="sm" or size="lg" to scale the tabs of any variant.

Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse.
JSX
import { Tabs, TabList, Tab, TabPanel } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Tabs defaultSelectedKey="home">
      <TabList aria-label="Underline example" variant="underline" size="sm">
        <Tab id="home">Home</Tab>
        <Tab id="profile">Profile</Tab>
        <Tab id="contact">Contact</Tab>
      </TabList>
      <TabPanel id="home">
        Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown
        aliqua, retro synth master cleanse.
      </TabPanel>
      <TabPanel id="profile">
        Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid.
        Exercitation +1 labore velit, blog sartorial PBR leggings.
      </TabPanel>
      <TabPanel id="contact">
        Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic
        lomo retro fanny pack lo-fi farm-to-table readymade.
      </TabPanel>
    </Tabs>
  )
}

Disabled tabs

Add disabled to a Tab to remove it from keyboard navigation and prevent it from being selected.

Raw denim you probably haven't heard of them jean shorts Austin.
JSX
import { Tabs, TabList, Tab, TabPanel } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Tabs defaultSelectedKey="home">
      <TabList aria-label="Example tabs with a disabled tab">
        <Tab id="home">Home</Tab>
        <Tab id="profile" disabled>
          Profile
        </Tab>
        <Tab id="contact">Contact</Tab>
      </TabList>
      <TabPanel id="home">
        Raw denim you probably haven't heard of them jean shorts Austin.
      </TabPanel>
      <TabPanel id="profile">This panel can't be reached — Profile is disabled.</TabPanel>
      <TabPanel id="contact">
        Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's.
      </TabPanel>
    </Tabs>
  )
}

Vertical

Pass orientation="vertical" to Tabs to stack the tabs. The list gets the flex-column class and aria-orientation="vertical", and the up and down arrow keys move between the tabs. The panels stay below the list unless the root is laid out: here className="d-flex align-items-start gap-md" puts them beside it.

Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse.
JSX
import { Tabs, TabList, Tab, TabPanel } from '@chassis-ui/react'

export const Example = () => {
  return (
    <Tabs
      className="d-flex align-items-start gap-md"
      defaultSelectedKey="home"
      orientation="vertical"
    >
      <TabList aria-label="Vertical example" variant="segments">
        <Tab id="home">Home</Tab>
        <Tab id="profile">Profile</Tab>
        <Tab id="contact">Contact</Tab>
      </TabList>
      <TabPanel id="home">
        Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown
        aliqua, retro synth master cleanse.
      </TabPanel>
      <TabPanel id="profile">
        Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid.
        Exercitation +1 labore velit, blog sartorial PBR leggings.
      </TabPanel>
      <TabPanel id="contact">
        Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic
        lomo retro fanny pack lo-fi farm-to-table readymade.
      </TabPanel>
    </Tabs>
  )
}

Overflow

Wrap the TabList in a NavOverflow, inside the Tabs, to move the tabs that don't fit on one line into a "More" menu. Set keepVisible on a Tab that should never move.

Controlled selection

Pass selectedKey/onSelectionChange to Tabs to control which tab is selected externally, e.g. to sync it with the URL. Omit them (use defaultSelectedKey instead) to let Tabs manage selection internally.

API

Tabs

PropTypeDefault
asChild?boolean—
Render the single child element in place of this component's own element, with this component's classes, props and ref merged onto it: <Button asChild><Link href="/login">Log in</Link></Button>. The alternative to component for React Server Components — an element can be passed from the server to this package's client components, a component reference (component={Link}) can't.
children?ReactNode—
A TabList (containing Tab children) followed by one TabPanel per tab.
className?string—
A string of all className you want applied to the base component.
component?ElementType—
Component used for the root node. Either a string to use a HTML element or a component.
defaultSelectedKey?Key—
The initially selected tab's key (uncontrolled).
disabledKeys?Iterable<Key>—
The keys of tabs that cannot be selected, focused, or otherwise interacted with.
keyboardActivation?"automatic" | "manual"—
Whether tabs are selected automatically on arrow-key focus ('automatic', the default) or only on explicit activation — Enter/Space or click ('manual').
onSelectionChange?((key: Key) => void)—
Callback fired when the selected tab changes.
orientation?"horizontal" | "vertical"—
The orientation of the tab list. 'vertical' stacks the tabs, with the flex-column class on the list, and moves between them with the up and down arrow keys. The panels stay below the list: to put them beside it, lay out the root with className.
ref?any—
selectedKey?Key—
The selected tab's key (controlled).

TabList

PropTypeDefault
childrenReactNode—
Tab elements — read as data by Tabs to build the tab collection (see Tabs.tsx). Not rendered directly.
className?string—
A string of all className you want applied to the base component.
size?"sm" | "lg"—
Size the component sm or lg.
variant?{ 'tabs' | 'segments' | 'underline' | 'pills' }tabs
Set the tab list variant to tabs, to segments (a segmented control) or to underline, which underlines the selected tab. 'pills' is the former name of 'segments': deprecated, it renders the same.

Tab

PropTypeDefault
childrenReactNode—
Label content for the tab. Must be a plain string for it to be used as the tab's accessible name when the rendered content itself doesn't expose one (e.g. an icon-only tab).
disabled?boolean—
Prevents the tab from being selected, focused, or otherwise interacted with.
idKey—
Identifies this tab and pairs it with the TabPanel of the same id.
keepVisible?boolean—
Keeps the tab in the list inside a NavOverflow, whatever the width.

TabPanel

PropTypeDefault
childrenReactNode—
Content of the panel, shown while the Tab of the same id is selected.
className?string—
A string of all className you want applied to the base component.
idKey—
Pairs this panel with the Tab of the same id.