Skip to content
EspañolEnglish
All components (204)

Tabs

Navigation · Tabs · .amset-tabs

What it is and when to use it

Several views of the same object in the same place, one at a time: the description, the measurements and the reviews of a product; the details, the invoices and the notices of a customer.

When to use it

  • When the content is split into three to six sections of the same size and whoever is looking needs only one at a time.
  • counter on a tab to say how many things are inside without having to open it ("Invoices 12").
  • disabled on a tab (or its key in disabledKeys) for a section that exists but cannot be viewed (no permissions): it looks dimmed and the keyboard skips it.
  • variant="pill" when they go inside a card or a bar, where one more line competes with the borders; orientation="vertical" to put them in a column next to the panel.
  • keyboardActivation="manual" when opening a tab is costly (a query to the server): the arrow keys only move focus and it is chosen with Enter or Space.

When not to

  • If each section is a page with its own address: that is navigation, SideNav or TopBar.
  • To change how the same list looks (list or grid, day or week): SegmentedGroup.
  • A tab without content has no panel: it is a filter or a mode of the same screen, drawn by whoever mounts the component. It is still a tab (arrows, focus, selected) but carries no aria-controls.
  • If several sections need to be seen at once or compared: Accordion with allowsMultipleExpanded.

Keyboard: Tab reaches the active tab; the left and right arrow keys (up and down in a column) change tab, skip the disabled ones and wrap around on reaching the end. Home and End go to the first and the last. In a right-to-left language the arrow keys of the row are reversed. Another Tab enters the panel. If the tabs do not fit in the width, the strip scrolls sideways without a bar: the mouse wheel moves it, and focus brings into view the tab it reaches.

Accessibility: it is a tablist with a name (label) and each panel is linked to its tab with aria-labelledby. Only the panel of the active tab is mounted. With line the active one is marked with color, underline and weight; with pill, filled: never with color alone.

Fine-tuning: CSS variables --amset-tabs-*: -font, -gap, -list-gap, -padding-block, -padding-inline, -radius. The size moves the padding and the font size; the variant, how the chosen one is marked.

What it looks like

Three items, €148.20.

<Tabs
  label="Order details"
  tabs={[
    { key: 'summary', label: 'Summary', content: <Resumen /> },
    { key: 'envio', label: 'Shipping', counter: 2, content: <Envio /> },
    { key: 'factura', label: 'Invoice', content: <Factura /> },
  ]}
/>

Props

Import it with import { Tabs } from '@amset/ui'. Required props are marked.

PropTypeWhat it does
labelrequiredstringWhat is announced on reaching the tab group.
tabsrequiredreadonly Tab[]—
classNamestring—
defaultSelectedKeystring | number—
disabledbooleanThe entire group is out of play: the chosen value is still visible, but cannot be changed.
disabledKeysIterable<string | number>—
keyboardActivation"manual" | "automatic"`automatic` (the default) chooses when the arrow moves; `manual` only moves focus and choosing is done with Enter or Space. Manual is for when opening a tab is costly: a server query per arrow press is not done.
onSelectionChange(key: string | number) => void—
orientation"horizontal" | "vertical"In a row (default) or in a column, beside the panel.
selectedKeystring | numberWhich one is chosen. If passed, the state is kept by whoever uses the component.
size"sm" | "md" | "lg"The font size and the padding of each tab.
variantTabsVariantHow the chosen one is marked: `line` underlines it (the default) and `pill` fills it. The pill one is for when the tabs sit inside a card or a bar, where one more line competes with the borders.

It also accepts the attributes of the element it renders: className, style, data-*, aria-* and its events.

CSS variables

These CSS variables are how you adapt it. Declare them on the component's selector, not on :root: the stylesheet redeclares them in the rule itself, and a declaration on the same element always wins.

VariableDefault
--amset-tabs-gapvar(--amset-space-4)
--amset-tabs-list-gapvar(--amset-space-1)
--amset-tabs-padding-blockvar(--amset-space-2)
--amset-tabs-padding-inlinevar(--amset-space-3)
--amset-tabs-fontvar(--amset-text-base)
--amset-tabs-radiusvar(--amset-radius)

How to change it

.amset-tabs {
  --amset-tabs-gap: ...;
  --amset-tabs-list-gap: ...;
}

In the test bench

Every state and variant, to try one at a time, on its page at ui.amset.tech: