Tabs

Import
import { Tabs } from "reshaped";
import type { TabsProps } from "reshaped";
Storybook
Supports 4 variants and 2 directions
Full keyboard navigation
Can be controlled and uncontrolled
Can be used as a form control
Works with any content


Tabs compound component consists of multiple subcomponents: List, Item and Panel that should be used together. Each Item and Panel should have unique matching IDs within the Tabs parent. As you can see, every Item has an icon and children support.

<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.

borderless variant is helpful when used with the parent component that already provides its border. For example, you can use it together with the Divider component and custom 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 />
</>

pills and pills-elevated variants can be used for the 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. That will allow you to render tabs as a menu on 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 are using a medium size, but you can switch it to the large size to increase the size of the triggers. It 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>

If you provide defaultValue to the component, it will select a tab on the component mount and keep the default selection logic later.

If you provide value, a controlled mode will be turned on, and you will have to change the component's state manually. This will still call the onChange handler, letting you customize the selection logic. For instance, 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 with 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.

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>
  );
}

This approach lets you easily customize their styles and animate their appearance using 3rd-party libraries.

Tabs can be used in forms as radio buttons if you pass the name property to them. That will automatically adjust their markup to native input elements, and the selected value will support 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 NextJS:

<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>

Tabs component provides all required aria- attributes and roles to its elements. To provide a description to the tablist element, you can use attributes={{ 'aria-label': 'Tabs label' }}.

Keyboard navigation:

When used as regular tabs:

  • Pressing Tab will place the focus on the active tab item when the focus moves into the tab list
  • Left and right arrow keys move focus across tab items
  • Space and Enter select the focused tab
  • Home moves focus to the first tab item
  • End moves focus to the last tab item

When used in forms:

  • Left and right arrow keys change the selected tab