Beta Preview

Tabs

Tabs organize content into multiple sections and allow users to navigate between them. The content under the set of tabs should be related and form a coherent unit.

Home
Files
Search
Settings
density 
orientation 
keyboardActivation 
isDisabled 
import {Tabs, TabList, Tab, TabPanel} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import Home from '@react-spectrum/s2/illustrations/gradient/generic2/Home';
import Folder from '@react-spectrum/s2/illustrations/gradient/generic2/FolderOpen';
import Search from '@react-spectrum/s2/illustrations/gradient/generic2/Search';
import Settings from '@react-spectrum/s2/illustrations/gradient/generic1/GearSetting';

<Tabs
  aria-label="Tabs"
  styles={style({minWidth: 250})}>
  <TabList aria-label="Tabs">
    <Tab id="home">Home</Tab>
    <Tab id="files">Files</Tab>
    <Tab id="search">Search</Tab>
    <Tab id="settings">Settings</Tab>
  </TabList>
  <TabPanel id="home">
    <div className={style({size: 'full', display: 'flex', alignItems: 'center', justifyContent: 'center'})}>
      <Home />
    </div>
  </TabPanel>
  <TabPanel id="files">
    <div className={style({size: 'full', display: 'flex', alignItems: 'center', justifyContent: 'center'})}>
      <Folder />
    </div>
  </TabPanel>
  <TabPanel id="search">
    <div className={style({size: 'full', display: 'flex', alignItems: 'center', justifyContent: 'center'})}>
      <Search />
    </div>
  </TabPanel>
  <TabPanel id="settings">
    <div className={style({size: 'full', display: 'flex', alignItems: 'center', justifyContent: 'center'})}>
      <Settings />
    </div>
  </TabPanel>
</Tabs>

Content

TabList follows the Collection Components API, accepting both static and dynamic collections. This example shows a dynamic collection, passing a list of objects to the items prop, and a function to render the children.

Tab 1
Tab 2
Tab 3
import {ActionButton, ActionButtonGroup, Tabs, TabList, Tab, TabPanel, Collection} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import {useState} from 'react';

function Example() {
  let [tabs, setTabs] = useState([
    {id: 1, title: 'Tab 1', content: 'Tab body 1'},
    {id: 2, title: 'Tab 2', content: 'Tab body 2'},
    {id: 3, title: 'Tab 3', content: 'Tab body 3'}
  ]);

  let addTab = () => {
    setTabs(tabs => [
      ...tabs,
      {
        id: tabs.length + 1,
        title: `Tab ${tabs.length + 1}`,
        content: `Tab body ${tabs.length + 1}`
      }
    ]);
  };

  let removeTab = () => {
    if (tabs.length > 1) {
      setTabs(tabs => tabs.slice(0, -1));
    }
  };

  return (
    <Tabs aria-label="Tabs" styles={style({width: 'full'})}>
      <div className={style({display: 'flex', alignItems: 'center'})}>
        <TabList
          items={tabs}
          styles={style({flexShrink: 1, flexGrow: 1, flexBasis: 'auto'})}>
          {item => <Tab>{item.title}</Tab>}
        </TabList>
        <ActionButtonGroup density="compact" size="S">
          <ActionButton onPress={addTab}>Add tab</ActionButton>
          <ActionButton onPress={removeTab}>Remove tab</ActionButton>
        </ActionButtonGroup>
      </div>
      <Collection items={tabs}>
        {item => <TabPanel>{item.content}</TabPanel>}
      </Collection>
    </Tabs>
  )
}

Icons

Tag supports icons and text as children. Text is always required, but can be hidden when the tabs are expanded using the labelBehavior prop.

Edit
Notifications
Likes
labelBehavior 
import {Tabs, TabList, Tab, TabPanel, Text} from '@react-spectrum/s2';
import Edit from '@react-spectrum/s2/icons/Edit';
import Bell from '@react-spectrum/s2/icons/Bell';
import Heart from '@react-spectrum/s2/icons/Heart';

<Tabs aria-label="Tabs">
  <TabList>
    <Tab id="edit">
      <Edit />
      <Text>Edit</Text>
    </Tab>
    <Tab id="bell">
      <Bell />
      <Text>Notifications</Text>
    </Tab>
    <Tab id="heart">
      <Heart />
      <Text>Likes</Text>
    </Tab>
  </TabList>
  <TabPanel id="edit">
    Review your edits
  </TabPanel>
  <TabPanel id="bell">
    Check your notifications
  </TabPanel>
  <TabPanel id="heart">
    See your likes
  </TabPanel>
</Tabs>

Overflow behavior

Horizontal tabs automatically collapse into a Picker when space is limited.

Home
Profile
Contact
About
import {Tabs, TabList, Tab, TabPanel} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};

<div
  className={style({
    width: 320,
    padding: 16,
    borderWidth: 1,
    borderStyle: 'solid',
    borderColor: 'gray-300',
    borderRadius: 'default',
    overflow: 'hidden',
    resize: 'horizontal'
  })}>
  <Tabs aria-label="Tabs">
    <TabList>
      <Tab id="home">Home</Tab>
      <Tab id="profile">Profile</Tab>
      <Tab id="contact">Contact</Tab>
      <Tab id="about">About</Tab>
    </TabList>
    <TabPanel id="home">
      Welcome home
    </TabPanel>
    <TabPanel id="profile">
      View your profile
    </TabPanel>
    <TabPanel id="contact">
      Find your contacts
      </TabPanel>
    <TabPanel id="about">
      Learn more
    </TabPanel>
  </Tabs>
</div>

Selection

Use the defaultSelectedKey or selectedKey prop to set the selected tab. The selected key corresponds to the id prop of a <Tab>. Tabs can be disabled with the isDisabled prop. See the selection guide for more details.

Home
Files
Search
Settings

Selected tab: files

import {Tabs, TabList, Tab, TabPanel, type Key} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import Home from '@react-spectrum/s2/illustrations/gradient/generic2/Home';
import Folder from '@react-spectrum/s2/illustrations/gradient/generic2/FolderOpen';
import Search from '@react-spectrum/s2/illustrations/gradient/generic2/Search';
import Settings from '@react-spectrum/s2/illustrations/gradient/generic1/GearSetting';
import {useState} from 'react';

function Example() {
  let [tab, setTab] = useState<Key>("files");
  return (
    <div>
      <Tabs
        aria-label="Tabs"
        selectedKey={tab}
        onSelectionChange={setTab}
      >
        <TabList aria-label="Tabs">
          <Tab id="home">Home</Tab>
          <Tab id="files">Files</Tab>
          <Tab id="search" isDisabled>Search</Tab>
          <Tab id="settings">Settings</Tab>
        </TabList>
        <TabPanel id="home">
          <div className={style({size: 'full', display: 'flex', alignItems: 'center', justifyContent: 'center'})}>
            <Home />
          </div>
        </TabPanel>
        <TabPanel id="files">
          <div className={style({size: 'full', display: 'flex', alignItems: 'center', justifyContent: 'center'})}>
            <Folder />
          </div>
        </TabPanel>
        <TabPanel id="search">
          <div className={style({size: 'full', display: 'flex', alignItems: 'center', justifyContent: 'center'})}>
            <Search />
          </div>
        </TabPanel>
        <TabPanel id="settings">
          <div className={style({size: 'full', display: 'flex', alignItems: 'center', justifyContent: 'center'})}>
            <Settings />
          </div>
        </TabPanel>
      </Tabs>
      <p>Selected tab: {tab}</p>
    </div>
  );
}

API

<Tabs>
  <TabList>
    <Tab />
  </TabList>
  <TabPanel />
</Tabs>

Tabs

Tabs organize content into multiple sections and allow users to navigate between them. The content under the set of tabs should be related and form a coherent unit.

NameTypeDefault
stylesDefault:
Spectrum-defined styles, returned by the style() macro.
childrenReactNodeDefault:
The content to display in the tabs.
density'compact''regular'Default: 'regular'
The amount of space between the tabs.
labelBehavior'show''hide'Default: 'show'
Defines if the text within the tabs should be hidden and only the icon should be shown. The text is always visible when the item is collapsed into a picker.
isDisabledbooleanDefault:
Whether the TabList is disabled. Shows that a selection exists, but is not available in that circumstance.
keyboardActivation'automatic''manual'Default: 'automatic'
Whether tabs are activated automatically on focus or manually.
orientationDefault: 'horizontal'
The orientation of the tabs.
selectedKeyKeynullDefault:
The currently selected key in the collection (controlled).
defaultSelectedKeyKeyDefault:
The initial selected key in the collection (uncontrolled).
onSelectionChange(key: Key) => voidDefault:
Handler that is called when the selection changes.
disabledKeysIterable<Key>Default:
The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with.

TabList

NameType
styles
Spectrum-defined styles, returned by the style() macro.
childrenReactNode(item: object) => ReactNode
The content to display in the tablist.
itemsIterable<T>
Item objects in the collection.
dependenciesReadonlyArray<any>
Values that should invalidate the item cache when using dynamic collections.

Tab

NameType
childrenReactNode
The content to display in the tab.
idKey
The unique id of the tab.
isDisabledboolean
Whether the tab is disabled.
styles
Spectrum-defined styles, returned by the style() macro.

TabPanel

NameTypeDefault
stylesDefault:
Spectrum-defined styles, returned by the style() macro.
childrenReactNodeDefault:
The content to display in the tab panels.
shouldForceMountbooleanDefault: false
Whether to mount the tab panel in the DOM even when it is not currently selected. Inactive tab panels are inert and cannot be interacted with. They must be styled appropriately so this is clear to the user visually.
idKeyDefault:
The unique id of the tab.

Testing

General setup

Tabs features automatic tab collapse behavior and may need specific mocks to test said behavior. TODO: update this with what mocks are required

Test utils

@react-spectrum/test-utils offers common tabs interaction utilities which you may find helpful when writing tests. To install, simply add it to your dev dependencies via your preferred package manager.

yarn add @react-spectrum/test-utils --dev

Once installed, you can access the User that @react-spectrum/test-utils provides in your test file as shown below. This user only needs to be initialized once and then can be used to generate the Tabs tester in your test cases. This gives you access to Tabs specific utilities that you can then call within your test to query for specific subcomponents or simulate common interactions. The example test case below shows how you might go about setting up the Tabs tester, use it to change tab selection, and verify the tabs' state after each interaction.

// Tabs.test.ts
import {render} from '@testing-library/react';
import {User} from '@react-spectrum/test-utils';

let testUtilUser = new User({interactionType: 'mouse'});
// ...

it('Tabs can change selection via keyboard', async function () {
  // Render your test component/app and initialize the listbox tester
  let {getByTestId} = render(
    <Tabs data-testid="test-tabs">
      ...
    </Tabs>
  );
  let tabsTester = testUtilUser.createTester('Tabs', {root: getByTestId('test-tabs'), interactionType: 'keyboard'});

  let tabs = tabsTester.tabs;
  expect(tabsTester.selectedTab).toBe(tabs[0]);

  await tabsTester.triggerTab({tab: 1});
  expect(tabsTester.selectedTab).toBe(tabs[1]);
});

See below for the full definition of the User and the Tabs tester.

Properties

NameTypeDefault
advanceTimer['advanceTimer']Default:
A function used by the test utils to advance timers during interactions. Required for certain aria patterns (e.g. table).
interactionType['interactionType']Default: mouse
The interaction type (mouse, touch, keyboard) that the test util user will use when interacting with a component. This can be overridden at the aria pattern util level if needed.

Methods

constructor(opts: ): void
createTester<T extends >(patternName: T, opts: <T>): <T>
Creates an aria pattern tester, inheriting the options provided to the original user.

Properties

NameType
activeTabpanelHTMLElementnull
Returns the currently active tabpanel if any.
selectedTabHTMLElementnull
Returns the currently selected tab in the tablist if any.
tabsHTMLElement[]
Returns the tabs in the tablist.
tabpanelsHTMLElement[]
Returns the tabpanels.
tablistHTMLElement
Returns the tablist.

Methods

constructor(opts: ): void
setInteractionType(type: ['interactionType']): void
Set the interaction type used by the tabs tester.
findTab(opts: {
tabIndexOrText: numberstring
}): HTMLElement
Returns a tab matching the specified index or text content.
triggerTab(opts: ): Promise<void>
Triggers the specified tab. Defaults to using the interaction type set on the tabs tester.

Testing FAQ