Skip to main content

Overview

Tabs organize content into multiple sections and allow users to navigate between them. Only one tab panel is visible at a time. Tabs are useful when you want to reduce vertical scrolling or separate content into logical groups. Tabs come in both horizontal and vertical orientations, with multiple visual variants to suit different design needs.

Installation

yarn add @twilio-paste/tabs

Usage

import { Tabs, TabList, Tab, TabPanels, TabPanel } from '@twilio-paste/tabs';

const MyTabs = () => (
  <Tabs selectedId="tab1">
    <TabList aria-label="My tabs">
      <Tab id="tab1">Tab 1</Tab>
      <Tab id="tab2">Tab 2</Tab>
      <Tab id="tab3">Tab 3</Tab>
    </TabList>
    <TabPanels>
      <TabPanel tabId="tab1">Content for Tab 1</TabPanel>
      <TabPanel tabId="tab2">Content for Tab 2</TabPanel>
      <TabPanel tabId="tab3">Content for Tab 3</TabPanel>
    </TabPanels>
  </Tabs>
);

Variants

Fitted Tabs

Fitted tabs expand to fill the width of their container.
<Tabs variant="fitted" selectedId="tab1">
  <TabList aria-label="Fitted tabs">
    <Tab id="tab1">Tab 1</Tab>
    <Tab id="tab2">Tab 2</Tab>
  </TabList>
  <TabPanels>
    <TabPanel tabId="tab1">Content 1</TabPanel>
    <TabPanel tabId="tab2">Content 2</TabPanel>
  </TabPanels>
</Tabs>

Vertical Tabs

<Tabs orientation="vertical" selectedId="tab1">
  <TabList aria-label="Vertical tabs">
    <Tab id="tab1">Tab 1</Tab>
    <Tab id="tab2">Tab 2</Tab>
  </TabList>
  <TabPanels>
    <TabPanel tabId="tab1">Content 1</TabPanel>
    <TabPanel tabId="tab2">Content 2</TabPanel>
  </TabPanels>
</Tabs>

Inverse Tabs

<Tabs variant="inverse" selectedId="tab1">
  <TabList aria-label="Inverse tabs">
    <Tab id="tab1">Tab 1</Tab>
    <Tab id="tab2">Tab 2</Tab>
  </TabList>
  <TabPanels>
    <TabPanel tabId="tab1">Content 1</TabPanel>
    <TabPanel tabId="tab2">Content 2</TabPanel>
  </TabPanels>
</Tabs>

Controlled Tabs

Use the useTabState hook to control tab state externally.
import { Tabs, TabList, Tab, TabPanels, TabPanel, useTabState } from '@twilio-paste/tabs';

const ControlledTabs = () => {
  const tab = useTabState({ selectedId: 'tab1' });

  return (
    <Tabs state={tab}>
      <TabList aria-label="Controlled tabs">
        <Tab id="tab1">Tab 1</Tab>
        <Tab id="tab2">Tab 2</Tab>
      </TabList>
      <TabPanels>
        <TabPanel tabId="tab1">Content 1</TabPanel>
        <TabPanel tabId="tab2">Content 2</TabPanel>
      </TabPanels>
    </Tabs>
  );
};

Disabled Tab

<Tabs selectedId="tab1">
  <TabList aria-label="Tabs with disabled">
    <Tab id="tab1">Tab 1</Tab>
    <Tab id="tab2" disabled>Tab 2 (Disabled)</Tab>
    <Tab id="tab3">Tab 3</Tab>
  </TabList>
  <TabPanels>
    <TabPanel tabId="tab1">Content 1</TabPanel>
    <TabPanel tabId="tab2">Content 2</TabPanel>
    <TabPanel tabId="tab3">Content 3</TabPanel>
  </TabPanels>
</Tabs>

Props

Tabs

children
React.ReactNode
The TabList and TabPanels components.
selectedId
string
The ID of the initially selected tab.
orientation
'horizontal' | 'vertical'
default:"horizontal"
Whether tabs are arranged horizontally or vertically.
variant
'fitted' | 'full_width' | 'inverse' | 'inverse_fitted' | 'inverse_full_width'
Changes the visual style of the tabs. Fitted variants make tabs equally fill the container width.
state
TabStateReturn
When using the useTabState hook to control tabs externally, pass the returned state here.
baseId
string
Base ID for generating tab and panel IDs.
element
string
default:"HORIZONTAL_TABS or VERTICAL_TABS"
Overrides the default element name to apply unique styles with the Customization Provider.

TabList

children
React.ReactNode
required
The Tab components to display.
aria-label
string
required
Required accessible label for the tab list. Describes the purpose of the tabs.
variant
Variants
Changes each Tab to either equally fit the width of the containing element or hug the contents of its label.
element
string
default:"HORIZONTAL_TAB_LIST or VERTICAL_TAB_LIST"
Overrides the default element name to apply unique styles with the Customization Provider.

Tab

children
React.ReactNode
required
The text or content to display in the tab.
id
string
Unique identifier for the tab. Used to associate with TabPanel via tabId.
disabled
boolean
Whether the tab is disabled and cannot be selected.
focusable
boolean
When an element is disabled, it may still be focusable. Works similarly to readOnly on form elements.
element
string
default:"HORIZONTAL_TAB or VERTICAL_TAB"
Overrides the default element name to apply unique styles with the Customization Provider.

TabPanels

children
React.ReactNode
required
The TabPanel components to display.
element
string
Overrides the default element name to apply unique styles with the Customization Provider.

TabPanel

children
React.ReactNode
required
The content to display when this tab is selected.
tabId
string
The ID of the Tab component this panel pairs with.
id
string
Unique identifier for the panel.
paddingTop
'space0'
Removes the default padding from the TabPanel by setting to ‘space0’.
element
string
default:"HORIZONTAL_TAB_PANEL or VERTICAL_TAB_PANEL"
Overrides the default element name to apply unique styles with the Customization Provider.

Accessibility

  • Tabs follow the ARIA Tabs pattern.
  • The TabList has a required aria-label to describe the purpose of the tabs.
  • Keyboard navigation:
    • Tab: Moves focus into and out of the tab list
    • Arrow keys: Move between tabs (Left/Right for horizontal, Up/Down for vertical)
    • Home/End: Jump to first/last tab
  • Selected tabs have aria-selected="true".
  • Each TabPanel is associated with its Tab via aria-labelledby.
  • Only the selected panel is rendered in the DOM and marked with aria-hidden="false".
  • Disabled tabs are not keyboard navigable and have aria-disabled="true".

Best Practices

Do

  • Use tabs to organize related content into logical groups
  • Keep tab labels short and descriptive (1-2 words)
  • Use horizontal tabs for most cases
  • Provide an aria-label on TabList that describes the purpose
  • Keep the number of tabs reasonable (typically 3-7)

Don’t

  • Don’t use tabs for sequential navigation (use a stepper instead)
  • Don’t nest tabs within tabs
  • Don’t use tabs if users need to see content from multiple tabs simultaneously
  • Don’t use tabs as a form of primary navigation

Build docs developers (and LLMs) love