Tab

One tab of a Tabs row: a label, an icon on any side of it, and - with a closeLabel - a close button of its own.

One tab of a Tabs row: a label, an icon on any side of it, and - with a closeLabel - a close button of its own.

<Tabs aria-label="Views" defaultValue="canvas">
  <Tab value="canvas" label="Canvas" />
  <Tab
    value="system"
    label="Design system"
    icon={<Icon name="folder" size="sm" />}
    iconPosition="start"
  />
</Tabs>

The icon is yours, at the size you give it - the tab places it and sizes nothing. Every label in a row sits on one line: a tab made taller by an icon above its label leaves the others' labels at its foot, level with its own.

Demos

For usage examples, visit the component demo page: Tabs

Import

import { Tab } from '@soroush.tech/design-system/Tabs'

Props

PropTypeDefaultDescription
valuestring | numberits positionWhat the tab is chosen as.
labelReactNode-The tab's label.
iconReactNode-The tab's icon, at the size you give it.
iconPosition'start' | 'end' | 'top' | 'bottom''top'Where icon sits around the label.
colorkeyof Theme['palette']its Tabs'The tab's accent - its selected label, outline and indicator, and its hover tint.
textColorkeyof Theme['text']its Tabs'The label's color.
sizekeyof Theme['sizes']its Tabs'Density - the label's font size and side padding; across a row, the row's tallest.
bgkeyof Theme['background']-The tab's ground while unselected.
selectedBgkeyof Theme['background']its Tabs' selectedBgThe tab's ground while selected.
borderRadiuskeyof Theme['radii']'sm' boxed or enclosedIts corner radius - in the enclosed look only the corners away from the divider.
isWrappedbooleanfalseThe label may take two lines.
disabledbooleanfalseThe tab can't be chosen, and the arrow keys pass it.
hrefstring-A link, for a navigation of pages.
asElementType'button', or 'a' with hrefThe element the tab is rendered as.
onClick(event) => void-Fired when the tab is pressed.
titlestring-Hover text.
closeLabelstring-Makes the tab closable: its close button's tooltip. Delete closes it from the keys.
secondaryActionReactNode-A control of the tab's own after its label - a kebab menu, a badge.

Like a table cell, a tab takes its row's look and its own value wins: the tab's prop, then what is set on its Tabs, then theme.components.Tab.defaultProps, then the row's own default. In the underline look the row's indicator takes the selected tab's color.

Closable tabs

<Tabs value={view} onChange={setView} onTabClose={closeTab}>
  <Tab value="canvas" label="Canvas" />
  <Tab value="button" label="Button" closeLabel="Close Button" />
</Tabs>

A tab with a closeLabel has a ✕ after its label: a button of its own beside the tab, outside the page's tab order. It shows on the selected tab and under the pointer, and keeps its room on the others, so nothing shifts when the pointer crosses a tab. A middle click on the tab and Delete on the focused tab close it too. A tab without a closeLabel never closes.

The ✕ is the pointer's: a tablist owns tabs alone, so it is hidden from assistive technology, with closeLabel as its tooltip. The keys' way is Delete, which the tab announces (aria-keyshortcuts="Delete"). With shouldAnnounceClose on Tabs it is a button named by closeLabel for assistive technology instead - a sibling of the tab inside the tab's role="presentation" wrapper, so nothing interactive is nested in the tab - still out of the tab order, and unseen (not visibility: hidden) on a tab that is not selected or hovered, so a screen reader still finds it.

A menu of the tab's own

<Tab
  value="light"
  label="Light"
  secondaryAction={
    <Menu
      align="end"
      trigger={
        <Button size="sm" variant="text" aria-label="Light options">
          ⋮
        </Button>
      }
    >
      <MenuItem value="rename">Rename...</MenuItem>
      <MenuItem value="delete">Delete</MenuItem>
    </Menu>
  }
/>

The secondary action sits beside the tab, not inside it, and keeps its own keys: in an open menu the arrow keys, Home and End move through its rows, not between the tabs.

Source code

If this page does not answer your question, have a look at the implementation of the component for more detail.