Skip to content
Autumn 2026 Mesh Release and Design Tokens v2 now available! Read the announcement

Tabs make it easy to view and navigate stacked panels of related content. They are especially useful when you need to show a lot of content in a small space.

Installation

bash
npm install @nib-group/mesh-tabs

Usage

jsx
import {Tabs, Tab, TabList, TabPanel} from '@nib-group/mesh-tabs';

This package contains a number of components built on top of the react-tabs library built by React.

Interactive demo

Tabs can be used in two different modes, controlled and uncontrolled.

Uncontrolled mode

This is the default mode which handles its state internally. You can change the starting tab with defaultIndex and you can listen for changes with onSelect.

In this mode you cannot force a tab change during runtime.

jsx
<Tabs>
  <TabList center={true}>
    <Tab>Tab 1</Tab>
    <Tab>Tab 2</Tab>
  </TabList>

  <TabPanel>
    <Box paddingVertical={{xs: 3, md: 4}}>
      <Box marginBottom={3}>
        <Heading size={2}>Any content 1</Heading>
      </Box>
      <Copy size="large">Neque a porta condimentum habitasse a maecenas a fames.</Copy>
    </Box>
  </TabPanel>
  <TabPanel>
    <Box paddingVertical={{xs: 3, md: 4}}>
      <Box marginBottom={3}>
        <Heading size={2}>Any content 2</Heading>
      </Box>
      <Box marginBottom={3}>
        <Copy size="large">Natoque iaculis a id ullamcorper lobortis aliquam magna massa nec ut scelerisque phasellus potenti torquent consectetur natoque.</Copy>
      </Box>
      <Copy>Ac adipiscing nulla facilisi ullamcorper ut ultricies scelerisque.</Copy>
    </Box>
  </TabPanel>
</Tabs>

Controlled mode

This mode has to be enabled by supplying selectedIndex to the <Tabs /> component.

In this mode tabs does not handle any tab selection state internally and leaves all the state management up to the outer application.

This mode also enforces you to set a handler for onSelect. defaultIndex does not have any effect and will therefore throw an error.

jsx
() => {
  const [selectedIndex, setSelectedIndex] = useState(0);

  return (
    <Tabs selectedIndex={selectedIndex} onSelect={setSelectedIndex}>
      <TabList center={true}>
        <Tab>Tab 1</Tab>
        <Tab>Tab 2</Tab>
      </TabList>

      <TabPanel>
        <Box paddingVertical={{xs: 3, md: 4}}>
          <Box marginBottom={3}>
            <Heading size={2}>Any content 1</Heading>
          </Box>
          <Copy size="large">Neque a porta condimentum habitasse a maecenas a fames.</Copy>
        </Box>
      </TabPanel>
      <TabPanel>
        <Box paddingVertical={{xs: 3, md: 4}}>
          <Box marginBottom={3}>
            <Heading size={2}>Any content 2</Heading>
          </Box>
          <Box marginBottom={3}>
            <Copy size="large">Natoque iaculis a id ullamcorper lobortis aliquam magna massa nec ut scelerisque phasellus potenti torquent consectetur natoque.</Copy>
          </Box>
          <Copy>Ac adipiscing nulla facilisi ullamcorper ut ultricies scelerisque.</Copy>
        </Box>
      </TabPanel>
    </Tabs>
  );
};

Props

Tabs

The following props are in addition to the react-tabs Tabs props.

PropTypeDefaultDescription
subtitlestringA subtitle added below the Tab title. Note: If one Tab uses a subtitle, then all Tabs must use a subtitle.
collapseBelowstring"xs" "sm" "md" "lg" "xl" "xxl" "xxxl"Tabs will be collapsed to stack view below the breakingpoint.
collapsedbooleanfalseToggles the first tab to be expanded on initial page load for mobile breakpoints. Only applies where collapseBelow is set.
showBorderbooleantrueToggles the border on each tab. We recommend enabling this prop on white or nearWhite backgrounds, to ensure users recognize inactive tabs as being interactive.
multiExpandedbooleanfalseWhen collapseMode is set,allowing multiple tabs to be expanded at once. It has no impact above the breakpoint.
sentimentSentimentTypeSets the sentiment used to theme the tabs, resolved against the active theme and applied as a data-sentiment attribute. Defaults prominence to "default" when only sentiment is provided.
prominenceProminenceTypeSets the prominence used to theme the tabs, resolved against the active theme and applied as a data-prominence attribute. Defaults sentiment to "default" when only prominence is provided.
activeTabSentimentSentimentTypeSets the sentiment for the selected tab (data-sentiment) and selected tab panel wrapper.
activeTabProminenceProminenceTypeSets the prominence for the selected tab (data-prominence) and selected tab panel wrapper.
inactiveTabSentimentSentimentTypeSets the sentiment for non-selected tabs (data-sentiment). Not applied to tab panel wrappers.
inactiveTabProminenceProminenceTypeSets the prominence for non-selected tabs (data-prominence). Not applied to tab panel wrappers.

TabList

The following props are in addition to the react-tabs TabList props.

PropTypeDefaultDescription
centerbooleanfalseAbove the lg breakpoint, when tabs do not take up the full width of the TabList they are left-aligned. This cannot be used alongside the fill prop.
fillbooleanfalseAbove the lg breakpoint, we can force the tabs to expand to take up the full width of the TabList by setting the fill prop to true. This cannot be used alongside the center prop.

Tab

The following props are in addition to the react-tabs Tabs props.

PropTypeDefaultDescription
truncatebooleanfalseTruncates Tab titles below a max-width (set per breakpoint). A value of ‘false’ will cause Tabs to grow to fit the Title string. Subtitles will always truncate when they exceed the width of the parent tab.

TabPanel

See react-tabs documentation for TabPanel props.

Design

By default, tabs are left aligned (note that this refers to the tab itself and not the tab label and supporting text which are left aligned on mobile and centred above the set desktop breakpoint). Tabs can also be set to centre themselves within the TabList, or can be set to fill the entire horizontal space.

To assist user understanding, as a default, there must always be one tab pre-selected, and the panel below it must be visible. Only a single content panel can be shown at a time. Tabs should always be placed above the content they relate to, never underneath. Additionally, the order of the tabs from left to right should never change, only the content panel should change.

You can use a minimum of two tabs at a time, but should be sure to only use a single row of tabs. Each of these things provide the visual cues and context to help a user understand tabs and their related content.

Color

A number of sentiments and prominences are available to theme the tabs.

jsx
<Tabs sentiment="warm" activeTabSentiment="default" inactiveTabSentiment="hero">
  <TabList>
    <Tab>Tab 1</Tab>
    <Tab>Tab 2</Tab>
  </TabList>

  <TabPanel>
    <Box paddingVertical={{xs: 3, md: 4}}>
      <Box marginBottom={3}>
        <Heading size={2}>Any content 1</Heading>
      </Box>
      <Copy size="large">Neque a porta condimentum habitasse a maecenas a fames.</Copy>
    </Box>
  </TabPanel>
  <TabPanel>
    <Box paddingVertical={{xs: 3, md: 4}}>
      <Box marginBottom={3}>
        <Heading size={2}>Any content 2</Heading>
      </Box>
      <Box marginBottom={3}>
        <Copy size="large">Natoque iaculis a id ullamcorper lobortis aliquam magna massa nec ut scelerisque phasellus potenti torquent consectetur natoque.</Copy>
      </Box>
      <Copy>Ac adipiscing nulla facilisi ullamcorper ut ultricies scelerisque.</Copy>
    </Box>
  </TabPanel>
</Tabs>

Responsive design

The collapseBelow prop swaps the Tabs out for an Accordion below the specified breakpoint. By default this is undefined and the Tabs are retained for all screen sizes.

Icons

Tabs should not include icons - we do not currently support adding icons to communicate the type of content a tab represents.

Touch UI

The tap area of the tabs are above the minimum recommended size requirements as referenced in Apple's Human Interface Guidelines.

Labels

Tab labels should provide clear and concise explanations of the content within. They should be no longer than one or two words and should be accurate in their description.

Each tab's content is independently categorised and mutually exclusive of the content of other tabs. Be sure to check your tabs make sense when read in isolation, especially in the context of screen reader usage.

Supporting text

In most cases we discourage the use of supporting text as the label itself should be well considered and provide enough context to the user. However if the supporting text is implemented (using the subtitle prop), then it should always be used to directly support the context of the label.

Accessibility

If the tabs are partially visible and if it is clicked, it will be scrolled to the center of the view.

If you are using a screen reader with Javascript turned on, you should be able to navigate the tabs pattern in the following way:

  • You can use left/right arrow, or up/down arrow to switch tabs
  • You can also use the TAB key to focus the tabs

The tab panel should have a heading to reduce confusion for people using screen readers, though this heading can be visually hidden.