tabs
Tabs let people switch between views of the same thing, such as the overview, the logs and the metrics of one service. The selected tab is underlined in blue.
level=info msg="rollout complete" pod=api-7f9c node=worker-2
when to use
- To switch between views of one thing that sit at the same level: the “overview”, “logs”, “metrics” and “events” of one service.
- When each view makes sense on its own, and people move between them in no fixed order.
- For two to six short labels that fit on one row.
when to use something else
- To filter one list. Use filter tabs: each tab shows a count over its label, and the list below follows the choice.
- To go to another page or app. Use links. For a home page of apps, use tiles in a tile group.
- For more views than fit on one row. Use a select. Today the row of tabs doesn't scroll, wrap or move the extra tabs into a menu.
- To turn a setting on or off. Use a switch.
anatomy
- Tab list. The row of tabs, with a thin line underneath.
- Tab. A label in monospace and lowercase, in grey, with room around it.
- Selection. The selected tab's label turns blue, and a blue underline lies over the row's line, under that tab.
- Panel. The view itself. It isn't part of the component: it goes right below the row, and what it needs is under accessibility.
variants and states
| state | looks |
|---|---|
| At rest | A grey label |
| Under the pointer | The label brightens |
| Selected | A blue label, with a blue underline |
| Focused | An outline inside the tab, when the keyboard brings focus to it |
| Disabled | Faded. It can't be focused or clicked, and the arrow keys pass over it |
behaviour
- A click selects a tab.
- The row is one tab stop, on the selected tab. The left and right arrow keys move along the row and select as they go, wrapping around at the ends. home and end jump to the first and the last tab that isn't disabled.
- Selection follows the arrow keys: every press switches the view. Keep the views quick to switch, or load a slow one only once the selection has settled.
The full keyboard map is on the tabs' React page.
accessibility
- A screen reader announces a row of tabs, each tab, and which one is selected.
- Name the panel. The tabs don't include the panel, so naming it is up to you: name it after the selected tab. When it holds nothing that can take focus, make it a tab stop, so people who use a keyboard can reach its content. The example at the top of this page does this.
Today: a disabled tab can't take focus, so people who use a screen reader don't learn that it exists.
content
- One or two words per tab, and nouns: “overview”, “logs”, “metrics”. No verbs and no counts; a count belongs in filter tabs.
- The same views in the same order on every object of a kind, so “logs” is always in the same place.
- Write the labels as they should be read. The lowercase look is only a style.
do and don't
do
don't
do
don't
do
don't
related
- Filter tabs: tabs that filter one list, with counts, a named row of tabs and the list connected to it.
- Select: when the views don't fit on one row.
- Tile group: for moving between apps rather than views.
specs
| state | spec |
|---|---|
| At rest | --fg-muted label |
| Under the pointer | --fg label |
| Selected | --cloud-blue label and a 2px --cloud-blue underline. Set with value (and onChange), or defaultValue |
| Focused | A 2px --focus-ring outline inside the tab (outline-offset: -2px), on :focus-visible |
| Disabled | disabled: true on an item. 40% opacity, a not-allowed cursor; a disabled button, so it is out of the tab order, and the arrow keys skip it |
- Tab list: a row with a 1px
--borderrule underneath,role="tablist". It doesn't scroll, wrap or overflow. - Tab: a
buttonwithrole="tab"andaria-selected: JetBrains Mono 500, 13px, padding 8px 12px,--fg-muted, lowercased withtext-transform. Only the selected tab hastabindex="0". - Underline: a 2px bottom border, pulled 1px down, so it lies over the list's rule.
- Items:
items, each with avalue, alabeland an optionaldisabled. WithoutvalueordefaultValue, the first item is selected;onChangereceives the new value. - Panel: not rendered. Give yours
role="tabpanel", anaria-labelafter the selected tab, andtabIndex={0}when it holds nothing focusable, as the first example does. - Motion: the label and underline colours change in 120ms
ease.