A bar of named views, one shown at a time, each tab in front of its own panel.
Options
| Choice | Values | Default | What it does |
|---|---|---|---|
looksmbk-tabs--look-<value> | boxedline | boxed | Boxed: the tabs in a well, the chosen one raised. Line: no well, a line under the row and a brand underline under the chosen tab, or a side bar beside it in a vertical bar. Figma calls this Style. |
sizesmbk-tabs--size-<value> | mdsm | md | |
orientationsmbk-tabs--orientation-<value> | horizontalvertical | horizontal | |
item-widthsmbk-tabs--item-width-<value> | equalhug | hug | Hug: each tab as wide as its words, and the bar as wide as its tabs, as Figma draws them. Equal: the bar spans the tabs' whole width, over their panel, and its tabs share it equally. A vertical bar's tabs are always as wide as its widest, so this changes the horizontal bar only. Not in Figma, which draws hug only. |
Examples
Usage
A bar of tabs, the selected one raised, its panel below.
With icons
An icon before each label.
Small
The small bar.
Vertical
The bar stacked beside the panel.
Line
The line look: no well, a line under the row, and a brand underline under the chosen tab, its label a weight bolder. Pointing at a tab fills it. A count in a tab is read with its name.
Line, vertical
The line look in a column: a side bar beside the chosen tab.
Guidelines
Purpose
Lets the reader switch between a few views of the same thing without leaving the page: one panel is shown, the rest are hidden, and the bar says which. It is drawn much like the segmented control on purpose, but a segmented control is a form input that picks a value; tabs change what is on the page.
The same bar also moves between a few pages of one subject, each with an address of its own: given links (TabsNav in React), each tab is a link and the bar is a navigation, the link to the page shown marked as the current page. A reader can then open any of them in a new browser tab, and each page brings its own content, so there are no panels.
When to use
- Two to six views of one subject: overview, details, history.
- The line look where a well would be too heavy, such as a second row under a page's header or tabs inside a card.
- Settings grouped into sections that fit on one page.
- The links form for a few pages of one subject, each with its own address: a project's overview, issues and releases.
When not to use
- Picking a value in a form. Use a segmented control or radios.
- Moving between a site's main sections. Use the header, the app bar or the sidebar.
- The tab list (the form with panels) to move between pages: its arrow keys would load each page they pass, and its pages could not be opened in a new browser tab. Use the links form.
- Steps in a sequence. Tabs imply the reader may go in any order.
Usage rules
Do
- Keep labels to one or two words.
- Put the most used view first and select it by default.
- Use the links form when each tab is a page with an address of its own.
Don't
- Use tabs for a single view. Show the content.
- Change the number of tabs while the reader is using them.
- Hide the current page behind the more tab when it could be one of the first few: order the links by how often they are used.
Keep tab labels to one or two words, so the bar fits and scans quickly.
Long labels make the bar wider than its space and slow to scan.
Show a single view as plain content, since a lone tab has nothing to switch to.
One tab on its own looks like a control that does nothing.
Anatomy
- The block, holding the list and the panels.
- The list part, the bar: a canvas well with a hairline border, at a fixed height for each size. By default it hugs its tabs, each as wide as its words; with equal item width it spans the tabs' whole width and its tabs share it equally.
- The indicator part, first in the bar: the selected tab's raised surface as one piece, which slides from tab to tab. Drawn only once the wrapper has placed it.
- The tab part, one button per view, filling the bar's height. The selected one is raised on the raised surface: the indicator's, or its own where there is no indicator.
- The icon part, optional, before the label part.
- The badge part, optional, after the label part: a badge saying the view holds something changed or new.
- The pause part, inside the label part when there is a badge: a comma only a screen reader hears.
- The panel part, one per tab; only the selected one is displayed.
- In the links form, the block is a navigation holding the list, and each tab is a link; the one to the page shown is marked as the current page. There are no panels.
- The more part, in the links form past a number of links: a tab at the row's end, after the rest, opening a menu of the links that did not fit, with the caret part after its words. While the page shown is one of them, it takes that page's name and is drawn as the chosen tab.
The line look draws no well: one subtle line runs under the row, and the chosen tab is marked by a brand underline across its whole width, the indicator, which slides from tab to tab, and its label one weight bolder. Each line tab is a step taller than a boxed one, with a rounded row inside it that shows as a fill when the pointer is over the tab. A vertical bar in the line look draws the line down its start edge and a brand side bar beside the chosen tab.
Behavior
Clicking a tab shows its panel. Selection can be controlled from outside or left to the component.
When the selection changes, the raised surface slides from the old tab to the new one with the state motion, the words change color with the feedback motion, and the new panel fades in with the enter motion, without moving. Nothing fades as the page loads, and a change of size or orientation, or tabs growing as fonts load, moves the surface at once rather than sliding. Under reduced motion the raised surface lands at once, and the color and the new panel still fade. Vertical tabs stack the bar beside the panels; the switch is a CSS change of layout, nothing else differs. A bar wider than the space it is given, such as several tabs with icons on a phone, scrolls sideways inside itself rather than widening the page.
In the links form a press on a link goes to its page, as any link does. When the page changes in place, without a new page load, the chosen tab's surface or underline slides to the new current page. Past the given number of links, the rest sit behind the more tab at the row's end, which opens a menu of them; the current page in that menu is drawn with the selected surface and its words a weight bolder.
On a touch screen every tab keeps its size and takes an invisible press area out to the comfortable touch target, reaching only halfway to the next tab; a bar that scrolls cuts the area off at its edge.
Accessibility
What it does for you
- The bar is a tab list. In the links form it is a navigation of links instead, with no tab roles.
- Left and right arrows (up and down when vertical) move between tabs and select as they go, following the reading direction, so in a right-to-left page the left arrow moves to the next tab; Home and End jump to the ends. Disabled tabs are skipped.
- Only the selected tab is in the tab order; Tab from it lands on the panel, which is focusable so its content can be reached. When no enabled tab is selected (the value names none, or a disabled one), the first enabled tab is in the tab order instead, so the list can always be reached.
- The focus ring is drawn just inside the tab's edge, because the bar's padding is too thin to hold it outside and a bar that scrolls cuts off anything past its padding.
- Each panel is labeled by its tab.
- A badge in a tab is read as part of the tab's name, after a pause: "Activity, 18".
- In the line look the chosen tab is marked by a shape, the underline or side bar, and a bolder label, not only by its color.
- In the links form the link to the page shown is marked as the current page, so a screen reader says which page this is. Tab moves from link to link and Enter follows one; the arrow keys are left to the page. The more tab is a button saying whether its menu is open; its menu is a named group of links, and Escape closes it and returns focus to the button.
- On a touch screen every tab takes an invisible press area out to the comfortable touch target, reaching only halfway to the next tab.
What you must do
- Supply an accessible name for the tab list.
- Give a dot badge a name of its own, such as "new", so the tab says what the dot means.
- In the links form, name the navigation, differently from any other navigation on the page, and mark the link to the page shown as current.
How to test
- Tab to the tabs: focus lands on the selected tab, with the focus ring just inside its edge, in both themes and in a forced-color mode. Press the arrow keys, Home and End: focus and selection move, skipping disabled tabs. Tab again lands on the panel.
- With a screen reader, the bar is read as a tab list by its name, each tab by its name with a badge's words after it, and each panel by its tab.
- With text at 200% and the page 320 pixels wide, a bar wider than its space scrolls sideways inside itself rather than widening the page.
- On a touch screen, a press just outside a tab's edge still chooses it.
- In the links form, Tab moves through the links one by one, the arrow keys move nothing, and a screen reader names the navigation and says which link is the current page. Open the more tab with Enter: its links are read as a group, and Escape returns to the tab.
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves focus to the selected tab, or to the first enabled tab when none is selected; pressed again, moves to the panel. |
| Right ArroworLeft Arrow | Moves to the next or previous tab and selects it; in a right-to-left page, Left Arrow moves to the next. In vertical tabs, Down Arrow and Up Arrow do this instead. |
| HomeorEnd | Moves to the first or last enabled tab and selects it. |
| Enter | In the links form, follows the focused link, or opens and closes the more tab's menu. |
| Escape | In the links form, closes the more tab's menu and returns focus to the more tab. |
Source
The files the tabs is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.
Stylesheet
/* smbk-tabs — Tabs. Figma "Tabs" 4171:15599 over "TabItem" 4170:15306.
* The bar is always the medium control height (default) or the small one
* (small), its tabs the smallest inset in from its edge and apart;
* the tabs stretch to fill it, with no vertical padding of their own, so the
* bar never grows past those heights.
* A bar of tabs in front of one panel each. The bar is drawn much like the
* segmented control (canvas well, selected tab raised on the raised surface);
* the difference is what it does, not how it looks.
* The same bar moves between pages when its tabs are links (TabsNav): a nav
* holding the bar, the link to the page shown marked aria-current="page", and
* the links past a number behind a More tab opening a dropdown of links, which
* draws as chosen (data-current) while the page shown is one of them. Every
* rule for a tab reaches the More tab inside its dropdown's anchor too. */
.smbk-tabs__list {
position: relative;
box-sizing: border-box;
display: inline-flex;
align-items: stretch;
gap: var(--smbk-space-inset-2xs);
height: var(--smbk-size-control-md);
/* Figma counts the edge in the bar's size and puts the padding inside it, as the segmented control's, so the
tabs sit the smallest inset plus the edge's width in from the bar's outer edge, leaving the smallest inset
between a tab and the edge: 34 and 26 tall. */
padding: var(--smbk-space-inset-2xs);
border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
border-radius: var(--smbk-radius-control-lg);
background: var(--smbk-color-surface-canvas);
}
/* A bar wider than its space (three tabs with icons on a phone) scrolls
sideways inside itself instead of widening the page, with the system's thin
scrollbar. Where the bar fits, nothing changes. A scrolling bar cuts off
whatever reaches past its padding, so a tab's focus ring is drawn inside
the tab's edge (below). A vertical bar keeps its tabs' width beside its
panel rather than shrinking into this scroll. */
.smbk-tabs__list {
max-width: 100%;
overflow-x: auto;
/* Set in a line by its top, not by its first item's words: the scrollbar the script adds as its first item
(scrollbar.ts) has none, and would set the bar lower. */
vertical-align: top;
scrollbar-width: thin;
scrollbar-color: var(--smbk-color-border-subtle) transparent;
}
.smbk-tabs__tab {
position: relative;
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--smbk-space-inline-sm);
margin: 0;
padding: 0 var(--smbk-space-inset-lg);
border: var(--smbk-border-width-hairline) solid transparent;
border-radius: var(--smbk-radius-control-sm);
background: transparent;
color: var(--smbk-color-text-subtle);
font-family: var(--smbk-typography-body-md-font-family);
font-size: var(--smbk-typography-body-md-font-size);
font-weight: var(--smbk-typography-body-md-font-weight);
line-height: var(--smbk-typography-body-md-line-height);
letter-spacing: var(--smbk-typography-body-md-letter-spacing);
text-decoration: none;
white-space: nowrap;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
-webkit-user-select: none;
user-select: none;
}
@media (hover: hover) {
.smbk-tabs__tab:hover {
color: var(--smbk-color-text-link-hover);
}
}
.smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]) {
border-radius: var(--smbk-radius-control-md);
background: var(--smbk-color-surface-raised);
border-color: var(--smbk-color-border-rim);
color: var(--smbk-color-text-default);
box-shadow: var(--smbk-elevation-control);
}
/* Inside the tab's edge: the bar's padding is thinner than the ring and its
gap, and the bar cuts off anything past its padding. */
/* The selected tab's surface as one piece that slides from tab to tab. The wrapper places it over the
selected tab and marks the bar; until then (markup written by hand, or before the page's script
runs) it is not drawn and the selected tab draws its own surface, which looks the same at rest.
It comes first in the bar, so the tabs paint over it. */
.smbk-tabs__indicator {
display: none;
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-rim);
border-radius: var(--smbk-radius-control-md);
background: var(--smbk-color-surface-raised);
box-shadow: var(--smbk-elevation-control);
pointer-events: none;
}
.smbk-tabs__list[data-indicator] > .smbk-tabs__indicator {
display: block;
}
.smbk-tabs__list[data-indicator] .smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]) {
background: transparent;
border-color: transparent;
box-shadow: none;
}
.smbk-tabs__tab:focus-visible {
outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
outline-offset: calc(-1 * var(--smbk-focus-ring-width));
}
.smbk-tabs__tab:disabled {
color: var(--smbk-color-text-disabled);
cursor: not-allowed;
}
.smbk-tabs__icon {
font-size: var(--smbk-size-icon-sm);
}
/* The comma a screen reader hears between the label and a badge ("Activity, 18"); never shown. */
/* .smbk-tabs__pause: hidden from view, kept for screen readers and the keyboard, by the shared rule in visually-hidden/visually-hidden.css. */
/* A mark after the label (a badge): it sits in the tab's row, spaced by the tab's own gap. */
.smbk-tabs__badge {
display: inline-flex;
align-items: center;
}
/* The More tab's caret, one step smaller than the tab's icon, as a caret may be. */
.smbk-tabs__caret {
font-size: var(--smbk-size-icon-xs);
}
.smbk-tabs__panel {
padding-top: var(--smbk-space-stack-lg);
color: var(--smbk-color-text-default);
}
.smbk-tabs__panel[hidden] {
display: none;
}
.smbk-tabs__panel:focus-visible {
outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
outline-offset: var(--smbk-focus-ring-offset);
}
/* ---- size: sm ---- */
/* Every size and orientation rule reaches only its own bar, tabs and panels (child selectors), so
tabs placed inside another tab's panel keep their own size and orientation. */
.smbk-tabs--size-sm > .smbk-tabs__list {
height: var(--smbk-size-control-sm);
border-radius: var(--smbk-radius-control-md);
}
.smbk-tabs--size-sm > .smbk-tabs__list .smbk-tabs__tab {
gap: var(--smbk-space-inline-xs);
padding: 0 var(--smbk-space-inset-md);
font-family: var(--smbk-typography-body-sm-font-family);
font-size: var(--smbk-typography-body-sm-font-size);
font-weight: var(--smbk-typography-body-sm-font-weight);
line-height: var(--smbk-typography-body-sm-line-height);
letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
}
.smbk-tabs--size-sm > .smbk-tabs__list .smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]),
.smbk-tabs--size-sm > .smbk-tabs__list > .smbk-tabs__indicator {
border-radius: var(--smbk-radius-control-sm);
}
.smbk-tabs--size-sm > .smbk-tabs__list .smbk-tabs__tab > .smbk-tabs__icon {
font-size: var(--smbk-size-icon-xs);
}
/* ---- item width: equal, the bar spanning the tabs' whole width over their panel, its tabs sharing
it equally as columns of one row. A space too narrow for them still scrolls. A vertical bar's
tabs already share its width, so only the horizontal bar changes. ---- */
.smbk-tabs--item-width-equal:not(.smbk-tabs--orientation-vertical) > .smbk-tabs__list {
display: grid;
grid-auto-columns: 1fr;
grid-auto-flow: column;
}
/* ---- orientation: vertical — a column of tabs beside the panel ---- */
.smbk-tabs--orientation-vertical {
display: flex;
align-items: flex-start;
gap: var(--smbk-space-inline-xl);
}
.smbk-tabs--orientation-vertical > .smbk-tabs__list {
flex-direction: column;
flex-shrink: 0;
align-items: stretch;
height: auto;
}
/* Figma lets a vertical tab hug its padding and a 16 icon: the small control height, at both densities. */
.smbk-tabs--orientation-vertical > .smbk-tabs__list .smbk-tabs__tab {
justify-content: flex-start;
height: var(--smbk-size-control-sm);
}
.smbk-tabs--orientation-vertical.smbk-tabs--size-sm > .smbk-tabs__list .smbk-tabs__tab {
height: var(--smbk-size-control-xs);
}
.smbk-tabs--orientation-vertical > .smbk-tabs__panel {
flex: 1 1 auto;
min-width: 0;
padding-top: 0;
}
/* ---- look: line — no well, one subtle line under the row (beside the column when vertical), and
the chosen tab marked by a brand underline (a side bar when vertical) at the emphasis width, a
shape as well as a color, its label a weight bolder. Each tab is the large control height (small:
the medium one) with the extra-small inset at its sides, around a row the medium control height
(small: the small one) padded by the medium inset; the row shows only as the fill a pointer
brings (Figma's TabItem Line, 18 October). The line is drawn inside the bar, and the chosen tab's
underline covers it across the whole tab. The indicator that slides is the underline; until the
wrapper places it, the chosen tab draws its own. ---- */
.smbk-tabs--look-line > .smbk-tabs__list {
gap: var(--smbk-space-inline-sm);
height: var(--smbk-size-control-lg);
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: inset 0 calc(-1 * var(--smbk-border-width-hairline)) 0 var(--smbk-color-border-subtle);
}
.smbk-tabs--look-line.smbk-tabs--size-sm > .smbk-tabs__list {
height: var(--smbk-size-control-md);
}
/* The tab's padding is the extra-small inset outside the row and the medium one inside it; it holds its own
layer, so the row's fill paints over the tab and under its words wherever the bar sits. */
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__tab,
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]) {
isolation: isolate;
gap: var(--smbk-space-inline-md);
padding: 0 calc(var(--smbk-space-inset-xs) + var(--smbk-space-inset-md));
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.smbk-tabs--look-line.smbk-tabs--size-sm > .smbk-tabs__list .smbk-tabs__tab {
gap: var(--smbk-space-inline-sm);
}
/* The icon follows the text: 20 beside Body MD, 16 beside Body SM, as Figma binds the line row's icon. */
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__tab > .smbk-tabs__icon {
font-size: var(--smbk-size-icon-md);
}
.smbk-tabs--look-line.smbk-tabs--size-sm > .smbk-tabs__list .smbk-tabs__tab > .smbk-tabs__icon {
font-size: var(--smbk-size-icon-sm);
}
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__tab > .smbk-tabs__caret {
font-size: var(--smbk-size-icon-sm);
}
.smbk-tabs--look-line.smbk-tabs--size-sm > .smbk-tabs__list .smbk-tabs__tab > .smbk-tabs__caret {
font-size: var(--smbk-size-icon-xs);
}
/* The row inside the tab, drawn only as the hover fill: the medium control height (small: the small one), the
extra-small inset in from the tab's sides, centered up and down, with the medium control corner (small: the small
one). */
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__tab::before {
content: '';
position: absolute;
inset-inline: var(--smbk-space-inset-xs);
top: 50%;
z-index: -1;
height: var(--smbk-size-control-md);
translate: 0 -50%;
border-radius: var(--smbk-radius-control-md);
background: transparent;
pointer-events: none;
transition: background-color var(--smbk-motion-feedback-duration) var(--smbk-motion-feedback-easing);
}
.smbk-tabs--look-line.smbk-tabs--size-sm > .smbk-tabs__list .smbk-tabs__tab::before {
height: var(--smbk-size-control-sm);
border-radius: var(--smbk-radius-control-sm);
}
/* The underline runs straight to the tab's ends; the focus ring alone takes a corner, Figma's ring's. */
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__tab:focus-visible {
border-radius: var(--smbk-radius-control-md);
}
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]) {
box-shadow: inset 0 calc(-1 * var(--smbk-border-width-emphasis)) 0 var(--smbk-color-border-brand);
}
/* The chosen tab's label a weight bolder, Body MD Medium (small: Body SM Medium). An invisible copy of its words at
that weight (`data-label`, which the wrapper writes) lies under every label with no height, so a label that turns
bolder never moves the tabs beside it. The copy is generated content, hidden, with empty words for assistive
technology, so the label is read once. */
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]) > .smbk-tabs__label {
font-family: var(--smbk-typography-body-md-medium-font-family);
font-weight: var(--smbk-typography-body-md-medium-font-weight);
}
.smbk-tabs--look-line.smbk-tabs--size-sm > .smbk-tabs__list .smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]) > .smbk-tabs__label {
font-family: var(--smbk-typography-body-sm-medium-font-family);
font-weight: var(--smbk-typography-body-sm-medium-font-weight);
}
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__label[data-label]::after {
content: attr(data-label);
content: attr(data-label) / "";
display: block;
height: 0;
overflow: hidden;
visibility: hidden;
font-family: var(--smbk-typography-body-md-medium-font-family);
font-weight: var(--smbk-typography-body-md-medium-font-weight);
pointer-events: none;
user-select: none;
}
.smbk-tabs--look-line.smbk-tabs--size-sm > .smbk-tabs__list .smbk-tabs__label[data-label]::after {
font-family: var(--smbk-typography-body-sm-medium-font-family);
font-weight: var(--smbk-typography-body-sm-medium-font-weight);
}
/* Pointing at a tab fills its row with the hover surface and its words take the default text color; a disabled tab
keeps its own color and no fill. */
@media (hover: hover) {
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__tab:not(:disabled):hover {
color: var(--smbk-color-text-default);
}
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__tab:not(:disabled):hover::before {
background: var(--smbk-color-surface-hover);
}
}
.smbk-tabs--look-line > .smbk-tabs__list > .smbk-tabs__indicator {
border: 0;
border-radius: 0;
background: transparent;
box-shadow: inset 0 calc(-1 * var(--smbk-border-width-emphasis)) 0 var(--smbk-color-border-brand);
}
.smbk-tabs--look-line > .smbk-tabs__list[data-indicator] .smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]) {
box-shadow: none;
}
/* Vertical: the line runs down the column's start edge, the side bar over it. Each line tab keeps the
horizontal tab's height, as Figma stacks the same tab item: the large control height, the medium one at the
small size. */
.smbk-tabs--look-line.smbk-tabs--orientation-vertical > .smbk-tabs__list {
height: auto;
box-shadow: inset var(--smbk-border-width-hairline) 0 0 var(--smbk-color-border-subtle);
}
.smbk-tabs--look-line.smbk-tabs--orientation-vertical > .smbk-tabs__list .smbk-tabs__tab {
height: var(--smbk-size-control-lg);
}
.smbk-tabs--look-line.smbk-tabs--orientation-vertical.smbk-tabs--size-sm > .smbk-tabs__list .smbk-tabs__tab {
height: var(--smbk-size-control-md);
}
.smbk-tabs--look-line.smbk-tabs--orientation-vertical > .smbk-tabs__list .smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]),
.smbk-tabs--look-line.smbk-tabs--orientation-vertical > .smbk-tabs__list > .smbk-tabs__indicator {
box-shadow: inset var(--smbk-border-width-emphasis) 0 0 var(--smbk-color-border-brand);
}
.smbk-tabs--look-line.smbk-tabs--orientation-vertical > .smbk-tabs__list[data-indicator] .smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]) {
box-shadow: none;
}
/* ---- motion: the indicator slides to the chosen tab with the state purpose, as a switch's thumb
does; the words change color with the feedback purpose; a newly shown panel fades in with the
enter purpose, without traveling. Only once the wrapper has placed the indicator, so nothing
fades in as the page loads. No movement under reduced motion. ---- */
.smbk-tabs__tab {
transition: color var(--smbk-motion-feedback-duration) var(--smbk-motion-feedback-easing);
}
.smbk-tabs__list[data-indicator] > .smbk-tabs__indicator {
transition-property: translate, width, height;
transition-duration: var(--smbk-motion-state-duration);
transition-timing-function: var(--smbk-motion-state-easing);
}
/* Set by the wrapper while it places the indicator without a slide. */
.smbk-tabs__list[data-indicator] > .smbk-tabs__indicator[data-instant] {
transition: none;
}
.smbk-tabs__list[data-indicator] ~ .smbk-tabs__panel {
transition: opacity var(--smbk-motion-enter-duration) var(--smbk-motion-enter-easing);
}
@starting-style {
.smbk-tabs__list[data-indicator] ~ .smbk-tabs__panel {
opacity: 0;
}
}
/* Under reduced motion the raised surface lands at once; the words' color and the new panel still fade. */
@media (prefers-reduced-motion: reduce) {
.smbk-tabs__list[data-indicator] > .smbk-tabs__indicator {
transition: none;
}
}
/* ---- touch: every tab takes an invisible press area ----
On a touch screen an invisible area, centred on each tab, takes the press out to the comfortable target in each
direction it falls short of it, and between two tabs only halfway into the bar's gap, so each tab stays one press;
a bar that scrolls cuts it off at its padding. The tabs, the bar, the focus ring and every place stay as drawn. */
@media (pointer: coarse) {
.smbk-tabs__tab::after {
content: '';
position: absolute;
inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
}
.smbk-tabs__list > .smbk-tabs__tab ~ .smbk-tabs__tab::after {
inset-inline-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inset-2xs) / -2);
}
.smbk-tabs__list > .smbk-tabs__tab:has(~ .smbk-tabs__tab)::after {
inset-inline-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inset-2xs) / -2);
}
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__tab ~ .smbk-tabs__tab::after {
inset-inline-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-sm) / -2);
}
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__tab:has(~ .smbk-tabs__tab)::after {
inset-inline-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-sm) / -2);
}
/* A column of tabs: the same, up and down. */
.smbk-tabs--orientation-vertical > .smbk-tabs__list .smbk-tabs__tab::after {
inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
}
.smbk-tabs--orientation-vertical > .smbk-tabs__list .smbk-tabs__tab ~ .smbk-tabs__tab::after {
inset-block-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inset-2xs) / -2);
}
.smbk-tabs--orientation-vertical > .smbk-tabs__list .smbk-tabs__tab:has(~ .smbk-tabs__tab)::after {
inset-block-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inset-2xs) / -2);
}
.smbk-tabs--look-line.smbk-tabs--orientation-vertical > .smbk-tabs__list .smbk-tabs__tab ~ .smbk-tabs__tab::after {
inset-block-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-sm) / -2);
}
.smbk-tabs--look-line.smbk-tabs--orientation-vertical > .smbk-tabs__list .smbk-tabs__tab:has(~ .smbk-tabs__tab)::after {
inset-block-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-sm) / -2);
}
}
/* ---- high contrast (forced colors) ---- */
/* In Windows high-contrast mode the browser drops fills and shadows; this puts the meaning back with the
user's own colors. Nothing here applies outside that mode. */
@media (forced-colors: active) {
/* The selected tab, or the indicator that slides under it once the wrapper has placed it,
filled with Highlight; the tab's words in HighlightText. */
.smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]) {
forced-color-adjust: none;
border-color: Highlight;
background: Highlight;
color: HighlightText;
box-shadow: none;
}
/* Its focus ring is drawn inside its edge, over the fill: a HighlightText ring as far in as a
ring usually stands out, so a Highlight edge stays around it. */
.smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]):focus-visible {
outline-color: HighlightText;
outline-offset: calc(-1 * (var(--smbk-focus-ring-width) + var(--smbk-focus-ring-offset)));
}
/* A badge in the selected tab is forced again: its own Canvas cap with CanvasText words. */
.smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]) .smbk-tabs__badge {
forced-color-adjust: auto;
color: CanvasText;
}
.smbk-tabs__indicator {
border-color: Highlight;
background: Highlight;
}
/* The line look keeps its underline or side bar in Highlight, with no fill behind the words, drawn
by the chosen tab itself, since the mode drops the indicator's shadow. */
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]),
.smbk-tabs--look-line > .smbk-tabs__list[data-indicator] .smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]) {
background: transparent;
color: CanvasText;
box-shadow: inset 0 calc(-1 * var(--smbk-border-width-emphasis)) 0 Highlight;
}
.smbk-tabs--look-line.smbk-tabs--orientation-vertical > .smbk-tabs__list .smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]),
.smbk-tabs--look-line.smbk-tabs--orientation-vertical > .smbk-tabs__list[data-indicator] .smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]) {
box-shadow: inset var(--smbk-border-width-emphasis) 0 0 Highlight;
}
.smbk-tabs--look-line > .smbk-tabs__list .smbk-tabs__tab:is([aria-selected="true"], [aria-current="page"], [data-current]):focus-visible {
outline-color: Highlight;
}
.smbk-tabs--look-line > .smbk-tabs__list > .smbk-tabs__indicator {
display: none;
}
}Tokens it reads
| Token and CSS | Value | Preview | Foundation |
|---|---|---|---|
| space.inset.2xs--smbk-space-inset-2xs | 2px | Spacing | |
| size.control.md--smbk-size-control-md | comfortable: 40pxcompact: 32pxchanges with density | Size | |
| border-width.hairline--smbk-border-width-hairline | 1px | Border width | |
| color.border.subtle--smbk-color-border-subtle | light: #c4cdd8dark: #2d3237changes with theme | Color | |
| radius.control.lg--smbk-radius-control-lg | 8px | Radius | |
| color.surface.canvas--smbk-color-surface-canvas | light: #e7ebf0dark: #111213changes with theme | Color | |
| space.inline.sm--smbk-space-inline-sm | comfortable: 6pxcompact: 4pxchanges with density | Spacing | |
| space.inset.lg--smbk-space-inset-lg | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| radius.control.sm--smbk-radius-control-sm | 4px | Radius | |
| color.text.subtle--smbk-color-text-subtle | light: #4e555edark: #9ba5b1changes with theme | Color | |
| typography.body-md--smbk-typography-body-md-font-family--smbk-typography-body-md-font-size--smbk-typography-body-md-font-weight--smbk-typography-body-md-line-height--smbk-typography-body-md-letter-spacing--smbk-typography-body-md-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 16pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| color.text.link-hover--smbk-color-text-link-hover | light: #111213dark: #f3f5f8changes with theme | Color | |
| radius.control.md--smbk-radius-control-md | 6px | Radius | |
| color.surface.raised--smbk-color-surface-raised | light: #f9fafbdark: #232527changes with theme | Color | |
| color.border.rim--smbk-color-border-rim | light: #ffffffe6dark: #ffffff0dchanges with theme | Color | |
| color.text.default--smbk-color-text-default | light: #232527dark: #e7ebf0changes with theme | Color | |
| elevation.control--smbk-elevation-control | light: 0px 0px 1px 0px #373d430d, 0px 1px 2px 0px #373d431a, 0px 2px 4px 0px #373d430ddark: 0px 0px 1px 0px #0000001a, 0px 1px 2px 0px #00000026, 0px 2px 4px 0px #0000001a | Elevation | |
| focus.ring.width--smbk-focus-ring-width | 2px | Focus | |
| color.border.focus--smbk-color-border-focus | light: #cc0d0ddark: #f34336changes with theme | Color | |
| color.text.disabled--smbk-color-text-disabled | light: #7c8692dark: #606a75changes with theme | Color | |
| size.icon.sm--smbk-size-icon-sm | 16px | Size | |
| size.icon.xs--smbk-size-icon-xs | 12px | Size | |
| space.stack.lg--smbk-space-stack-lg | comfortable: 16pxcompact: 12pxchanges with density | Spacing | |
| focus.ring.offset--smbk-focus-ring-offset | 2px | Focus | |
| size.control.sm--smbk-size-control-sm | comfortable: 32pxcompact: 28pxchanges with density | Size | |
| space.inline.xs--smbk-space-inline-xs | 4px | Spacing | |
| space.inset.md--smbk-space-inset-md | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| typography.body-sm--smbk-typography-body-sm-font-family--smbk-typography-body-sm-font-size--smbk-typography-body-sm-font-weight--smbk-typography-body-sm-line-height--smbk-typography-body-sm-letter-spacing--smbk-typography-body-sm-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| space.inline.xl--smbk-space-inline-xl | comfortable: 16pxcompact: 12pxchanges with density | Spacing | |
| size.control.xs--smbk-size-control-xs | 24px | Size | |
| size.control.lg--smbk-size-control-lg | comfortable: 48pxcompact: 40pxchanges with density | Size | |
| space.inline.md--smbk-space-inline-md | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| space.inset.xs--smbk-space-inset-xs | 4px | Spacing | |
| size.icon.md--smbk-size-icon-md | 20px | Size | |
| motion.feedback.duration--smbk-motion-feedback-duration | 150ms | Motion | |
| motion.feedback.easing--smbk-motion-feedback-easing | cubic-bezier(0.16, 1, 0.3, 1) | Motion | |
| border-width.emphasis--smbk-border-width-emphasis | 2px | Border width | |
| color.border.brand--smbk-color-border-brand | #cc0d0d | Color | |
| typography.body-md-medium--smbk-typography-body-md-medium-font-family--smbk-typography-body-md-medium-font-size--smbk-typography-body-md-medium-font-weight--smbk-typography-body-md-medium-line-height--smbk-typography-body-md-medium-letter-spacing--smbk-typography-body-md-medium-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 16pxfontWeight: 500lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| typography.body-sm-medium--smbk-typography-body-sm-medium-font-family--smbk-typography-body-sm-medium-font-size--smbk-typography-body-sm-medium-font-weight--smbk-typography-body-sm-medium-line-height--smbk-typography-body-sm-medium-letter-spacing--smbk-typography-body-sm-medium-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 500lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| color.surface.hover--smbk-color-surface-hover | light: #373d4326dark: #606a754dchanges with theme | Color | |
| motion.state.duration--smbk-motion-state-duration | 200ms | Motion | |
| motion.state.easing--smbk-motion-state-easing | cubic-bezier(0.65, 0, 0.35, 1) | Motion | |
| motion.enter.duration--smbk-motion-enter-duration | 320ms | Motion | |
| motion.enter.easing--smbk-motion-enter-easing | cubic-bezier(0.16, 1, 0.3, 1) | Motion | |
| size.target.comfortable--smbk-size-target-comfortable | 44px | Size |
Manifest
{
"name": "tabs",
"block": "smbk-tabs",
"summary": "A bar of named views, one shown at a time, each tab in front of its own panel.",
"adapt": "css",
"axes": {
"look": {
"values": [
"boxed",
"line"
],
"default": "boxed",
"description": "Boxed: the tabs in a well, the chosen one raised. Line: no well, a line under the row and a brand underline under the chosen tab, or a side bar beside it in a vertical bar. Figma calls this Style."
},
"size": {
"values": [
"md",
"sm"
],
"default": "md"
},
"orientation": {
"values": [
"horizontal",
"vertical"
],
"default": "horizontal"
},
"item-width": {
"values": [
"equal",
"hug"
],
"default": "hug",
"description": "Hug: each tab as wide as its words, and the bar as wide as its tabs, as Figma draws them. Equal: the bar spans the tabs' whole width, over their panel, and its tabs share it equally. A vertical bar's tabs are always as wide as its widest, so this changes the horizontal bar only. Not in Figma, which draws hug only."
}
},
"flags": {},
"parts": [
"list",
"indicator",
"tab",
"icon",
"label",
"pause",
"badge",
"more",
"caret",
"panel"
],
"figmaNodeId": "4171:15599"
}