Tabs

Import
import { Tabs } from "reshaped";
import type { TabsProps } from "reshaped";
Storybook

Tabs compound component consists of multiple subcomponents: Tabs.List, Tabs.Item, and PTabs.anel, which should be used together. Each Tabs.Item and Tabs.Panel should have a unique matching id within the Tabs parent. Every Tabs.Item supported rendering an icon and gives you full composition control using children.

<Tabs>
  <Tabs.List>
    <Tabs.Item value="1" icon={IconZap}>
      Item 1
    </Tabs.Item>
    <Tabs.Item value="2" icon={IconZap}>
      Long item 2
    </Tabs.Item>
    <Tabs.Item value="3" icon={IconZap}>
      Very long item 3
    </Tabs.Item>
  </Tabs.List>

  <Tabs.Panel value="1">Tab 1</Tabs.Panel>
  <Tabs.Panel value="2">Tab 2</Tabs.Panel>
  <Tabs.Panel value="3">Tab 3</Tabs.Panel>
</Tabs>

Tabs support pills, pills-elevated, and borderless variants.

The borderless variant is helpful when used with a parent component that already provides its border. For example, you can use it together with the Divider component and customize the padding value.

<>
  <View paddingInline={8}>
    <Tabs variant="borderless">
      <Tabs.List>
        <Tabs.Item value="1">Item 1</Tabs.Item>
        <Tabs.Item value="2">Long item 2</Tabs.Item>
        <Tabs.Item value="3">Very long item 3</Tabs.Item>
      </Tabs.List>
    </Tabs>
  </View>
  <Divider blank />
</>

The pills and pills-elevated variants can be used for less prominent secondary navigation.

<View gap={3}>
  <Tabs variant="pills">
    <Tabs.List>
      <Tabs.Item value="1">Item 1</Tabs.Item>
      <Tabs.Item value="2">Long item 2</Tabs.Item>
      <Tabs.Item value="3">Very long item 3</Tabs.Item>
    </Tabs.List>
  </Tabs>

  <Tabs variant="pills-elevated">
    <Tabs.List>
      <Tabs.Item value="1">Item 1</Tabs.Item>
      <Tabs.Item value="2">Long item 2</Tabs.Item>
      <Tabs.Item value="3">Very long item 3</Tabs.Item>
    </Tabs.List>
  </Tabs>
</View>

You can change the direction of the tab items from row to column. This allows you to render tabs as a menu on the start or end side of the content.

<Tabs direction="column">
  <View direction="row" gap={4}>
    <View width="150px">
      <Tabs.List>
        <Tabs.Item value="1" icon={IconZap}>
          Item 1
        </Tabs.Item>
        <Tabs.Item value="2" icon={IconZap}>
          Long item 2
        </Tabs.Item>
        <Tabs.Item value="3" icon={IconZap}>
          Very long item 3
        </Tabs.Item>
      </Tabs.List>
    </View>

    <View.Item grow>
      <Tabs.Panel value="1">Tab 1</Tabs.Panel>
      <Tabs.Panel value="2">Tab 2</Tabs.Panel>
      <Tabs.Panel value="3">Tab 3</Tabs.Panel>
    </View.Item>
  </View>
</Tabs>

By default, Tabs use a medium size, but you can switch it to large to increase the size of the triggers. This can be helpful for marketing pages or when you want to fit more than just a text label inside the trigger.

<Tabs size="large">
  <Tabs.List>
    <Tabs.Item value="1" icon={IconZap}>
      Item 1
    </Tabs.Item>
    <Tabs.Item value="2" icon={IconZap}>
      Long item 2
    </Tabs.Item>
    <Tabs.Item value="3" icon={IconZap}>
      Very long item 3
    </Tabs.Item>
  </Tabs.List>
</Tabs>

Providing a defaultValue to the component selects a tab on component mount and retains the default selection logic afterward.

Providing a value enables controlled mode, requiring manual state changes. This still triggers the onChange handler, allowing customization of the selection logic. For example, you can fetch data for the tab before making it active.

<Tabs defaultValue="2">
  <Tabs.List>
    <Tabs.Item value="1">Item 1</Tabs.Item>
    <Tabs.Item value="2">Long item 2</Tabs.Item>
    <Tabs.Item value="3" icon={IconZap}>
      Very long item 3
    </Tabs.Item>
  </Tabs.List>

  <Tabs.Panel value="1">Tab 1</Tabs.Panel>
  <Tabs.Panel value="2">Tab 2</Tabs.Panel>
  <Tabs.Panel value="3">Tab 3</Tabs.Panel>
</Tabs>

You can stretch the tab items to keep their width equal using the itemWidth property.

<Tabs itemWidth="equal">
  <Tabs.List>
    <Tabs.Item value="1">Item 1</Tabs.Item>
    <Tabs.Item value="2">Item 2</Tabs.Item>
    <Tabs.Item value="3">Item 3</Tabs.Item>
  </Tabs.List>

  <Tabs.Panel value="1">Tab 1</Tabs.Panel>
  <Tabs.Panel value="2">Tab 2</Tabs.Panel>
  <Tabs.Panel value="3">Tab 3</Tabs.Panel>
</Tabs>

Panels don't have to be located at the top level of the component. You can place them anywhere inside the Tabs scope, and they will still work as expected.

This approach allows you to easily customize their styles and animate their appearance using third-party libraries.

function TabsExample() {
  const [value, setValue] = React.useState("1");

  return (
    <Tabs value={value} onChange={({ value }) => setValue(value)}>
      <Tabs.List>
        <Tabs.Item value="1">Item 1</Tabs.Item>
        <Tabs.Item value="2">Item 2</Tabs.Item>
      </Tabs.List>
      {value === "1" && (
        <View padding={4}>
          <Tabs.Panel value="1">Tab 1</Tabs.Panel>
        </View>
      )}
      {value === "2" && (
        <View padding={4}>
          <Tabs.Panel value="2">Tab 2</Tabs.Panel>
        </View>
      )}
    </Tabs>
  );
}

Tabs can be used in forms as radio buttons if you pass the name property to them. This automatically adjusts their markup to native input elements, and the selected value supports form submission.

<Tabs variant="pills-elevated" name="tabs">
  <Tabs.List>
    <Tabs.Item value="1">Item 1</Tabs.Item>
    <Tabs.Item value="2">Long item 2</Tabs.Item>
    <Tabs.Item value="3">Very long item 3</Tabs.Item>
  </Tabs.List>
</Tabs>

Due to Tabs composition flexibility, you can use it together with router libraries by wrapping Tabs.Item with the router link component. For example, you can use this approach to integrate it with Next.js:

<Tabs defaultValue="map">
  <Tabs.List>
    <NextLink href="#map" passHref legacyBehavior>
      <Tabs.Item value="map">Map view</Tabs.Item>
    </NextLink>
    <NextLink href="#list" passHref legacyBehavior>
      <Tabs.Item value="list">List view</Tabs.Item>
    </NextLink>
  </Tabs.List>
</Tabs>

When used as regular tabs:

  • Tab - place focus on the active tab item when the focus moves into the tab list.
  • ArrowLeft / ArrowRight - move focus across tab items.
  • Space / Enter - select the focused tab.
  • Home - move focus to the first tab item.
  • End - moves focus to the last tab item.

When used in forms:

  • ArrowLeft / ArrowRight - change the selected tab.
  • Tabs component provides all required ARIA attributes and roles to its elements.
  • To provide a description to the tablist element, use attributes={{ 'aria-label': 'Tabs label' }}.