filter tabs
Filter tabs show the key figures of one list as a row of tabs, each a count over a label, such as “3” over “needs attention”. Choosing a tab filters the list below it.
when to use
- At the top of one list, to show the few parts of it that people act on, and to let them pick one: “missing”, “on their way”, “needs attention”.
- When each part's count is worth seeing before people choose it.
- For one part at a time, out of two to about six. The worklist puts them above its table.
when to use something else
- To switch between views of one thing, such as its overview, logs and metrics. Use tabs.
- For filters people combine, such as a kind, and a time, and a state. Use the facets of a filter bar, which combine.
- For a figure that filters nothing. Show the number as text, or as a badge beside what it counts.
- To go to another page. Use a link.
anatomy
- Row of tabs. The tabs side by side, with a thin line under the row. When they don't all fit, the row scrolls sideways, and the edge where more tabs are cut off fades out.
- Tab. A block of monospace text, aligned to the left, with a minimum width. The count sits on top, but the label is read first: “needs attention 3”.
- Count (optional). A large number with thousands separators, such as “2,500”, in bright text or in the colour of the tab's state.
- Label. Small, lowercase and on one line, in a muted colour that brightens under the pointer and when the tab is selected.
- Selection. A blue underline under the selected tab, drawn over the row's line.
- Panel. Yours: the list the tabs filter. It isn't part of the component; accessibility says how it is named.
variants and states
| state | looks |
|---|---|
| At rest | A muted label; the count in bright text, or in its state's colour |
| Under the pointer | A slightly lighter fill, and a bright label |
| Selected | A bright label, and a blue underline |
| Keyboard focus | An outline inside the tab |
| Disabled | Dimmed. It can't be focused or chosen, and the arrow keys pass over it |
states on counts
A state colours the count, and only the count, when the number carries a meaning: error for “needs attention”, warning for “can't be checked”, information for “on their way”. These are the colours of the status vocabulary.
Leave the state off for counts that only say how many, such as “missing” or “not yet aired”: they stay plain. A tab without a count shows no state at all. The example at the top of this page uses both kinds.
Plain counts, with no state anywhere:
Leave the count out when the number isn't known. The tab then shows its label alone.
behaviour
- A click on a tab selects it.
- The row is one tab stop: tab goes to the selected tab, and the next tab leaves the row.
- The selection follows focus. The left and right arrow keys move to the previous or the next tab and select it, so every press filters the list. home and end go to the first and the last tab.
- Because every press filters, keep the filtering quick. Usually it is, as the list is filtered on the page itself. For a list that is slow to load, let the selection settle before the list loads.
The full keyboard map is on the filter tabs' React page.
accessibility
- The row of tabs must have a name: what it filters, and by what, such as “filter missing by state”. Only the selected tab is in the tab order.
- A tab's name is its label, then its count: “needs attention 3”.
- The list below is the tabs' panel, and it is named after the selected tab: “needs attention 3”.
- The colour of a count is a second signal; the label says what the count means. A count has no glyph, unlike a status, so never let the colour say what the label doesn't.
- When a count changes, it isn't announced.
Today: disabled tabs can't take focus, so people who use a screen reader don't learn that they exist.
content
- Label. What the tab keeps, in the list's own words: “needs attention”, “on their way”, “can't be checked”. A few words, in lowercase.
- Count. Exact, not rounded. Leave it out when it isn't known, rather than showing “0”.
- First tab. The whole list, such as “missing” or “all”, so there is always a way back.
- The row's name. What it filters, and by what: “filter missing by state”.
do and don't
related
- Tabs: views of one thing, not filters of one list.
- Worklist: puts the tabs above the list, and connects the panel.
- Filter bar: filters that combine, a search and facets.
- Status vocabulary: the states the counts take their colours from.
specs
| part | in code | spec |
|---|---|---|
| Row | FilterTabs, named by label (required) |
role="tablist", a 1px --border rule below. Scrolls sideways when the tabs don't fit; a cut-off edge fades out over 48px. |
| Tab | tabs: { id, label, count, state, disabled } |
A button with role="tab". At least 96px wide, padding 8px 16px 12px, JetBrains Mono, left-aligned. The label comes first, so it is read first; the count is drawn on top of it, 2px above. |
| Count | count (leave it out when unknown) |
20px, weight 600, line height 1.1, letter-spacing -0.01em, tabular figures, with thousands separators. --fg, or the colour of its state. |
| Label | label |
12px, weight 500, line height 1.3, lowercased with text-transform, one line, --fg-muted; --fg on hover and when selected. |
| Selection | value and onChange |
A 2px --cloud-blue underline, laid over the row's rule. |
| Panel | panelId and idPrefix |
Yours: role="tabpanel", named by the selected tab with aria-labelledby={filterTabId(idPrefix, value)}. Every tab points at it with aria-controls. |
| state | spec |
|---|---|
| Rest | Label in --fg-muted; count in --fg or in its state's colour |
| Hover | --surface fill, label in --fg |
| Selected | Label in --fg, a 2px --cloud-blue underline |
| Focus | A 2px --focus-ring outline inside the tab |
| Disabled | disabled on the tab: 45% opacity, a not-allowed cursor. Not focusable, not chosen, skipped by the arrow keys |
- States on counts:
stateon a tab.'information','success','warning'and'error'colour the count in--state-information,--state-success,--state-warningand--state-error;'neutral', or no state, leaves it--fg. - Keys: automatic activation, so the selection follows focus. Only the selected tab has
tabindex="0". The left and right arrow keys select the previous or the next enabled tab, wrapping at the ends; home and end select the first and the last. - Motion: colour, underline and fill change in 120ms
ease.