Several buttons acting as one control: spaced apart, or segmented into one bar with dividers.
Options
| Choice | Values | Default | What it does |
|---|---|---|---|
typesmbk-btn-group--type-<value> | defaultsegmented | segmented | Default keeps buttons apart. Segmented fuses them into one bar with hairline dividers. |
tonesmbk-btn-group--tone-<value> | brandsecondaryoutline | secondary | Which action family the fused bar draws its dividers and border from. Buttons inside an outline bar use the ghost tone. |
sizesmbk-btn-group--size-<value> | lgmdsm | md | Matches the size of the buttons inside, md like the button; sets the gap and the bar radius. |
orientationsmbk-btn-group--orientation-<value> | horizontalvertical | horizontal |
Examples
Usage
The default type: separate buttons with a gap, one primary.
Segmented
Several equal choices fused into one bar, with a hairline divider in the tone's color.
Sizes
Three heights, matching the button.
Wrapping
The default type in a phone-wide space: the buttons that do not fit wrap onto a second line. A segmented bar stays on one line.
Vertical
A fused bar stacked top to bottom.
Guidelines
Purpose
Several buttons that belong together, presented as one control. Kept apart, they are simply a row with the right gap that wraps when it runs out of room. Segmented, they fuse into one bar with shared edges and hairline dividers, which is how a set of mutually exclusive choices reads as a single thing.
When to use
- A small set of exclusive choices where a segmented control would be too small.
- A row of actions that must stay together on a narrow page: the default type wraps onto a second line rather than running off the edge.
- Two or three related actions of equal weight, kept apart with the default type.
When not to use
- For a main action with a menu of related ones. Use the split button.
- For a single toggle between views. Use the segmented control.
- For unrelated actions that happen to sit side by side. Give them their own spacing.
Usage rules
Do
- Give every button in a fused bar the same size as the group.
- Use ghost buttons inside an outline group; the group draws the border.
Don't
- Mix tones inside a segmented bar. The dividers are tuned to one fill.
- Build a main action with a menu from a segmented bar and a dropdown. Use the split button.
- Put a link styled as a button next to real buttons in a fused bar without checking focus order.
Give every button in a fused bar the group's tone, so the dividers match and the bar reads as one control.
Mixed tones in one bar break the dividers and make one choice look more important.
Give every button the group's size, so the bar keeps one height.
Buttons of different sizes in one bar leave steps along its edges.
Anatomy
The block is the container. Its children are buttons, unchanged; the group squares their inner corners, rounds the bar's outer corners, and draws the divider between neighbors. The outline tone draws the bar's border itself, so the buttons inside use the ghost tone.
A button that opens more options is a dropdown whose opener is that button, placed in the group where the button would stand. The dropdown holds its opener and its panel together, and the group passes its corners, its divider and its edge on to the opener, so it looks like any other button in the bar while the dropdown does the menu's keyboard and focus work.
Behavior
None of its own. Each button behaves as a button. The group is a layout and a shared edge.
The default type, in a row, never grows wider than the space it is given: when its buttons do not fit on one line, the next ones wrap onto a second line, with a small gap between the lines. A segmented bar and a vertical group stay as they are.
Accessibility
What it does for you
- The container carries the group role, and the wrapper requires an accessible name for it.
- Focus moves through the buttons in order.
- Because the fused bar hides the buttons' own borders, focus rings are what separates the focused button from its neighbors. They are never suppressed, and a focused button sits above its neighbors so the shared edge never covers its ring.
What you must do
- Give the group an accessible name that says what the group is for.
How to test
- Tab through the group: focus moves from button to button in order, and each focus ring shows whole, in both themes and in a forced-color mode.
- Press Enter, then Space on a button: each runs its action once.
- With a screen reader, the group is read by its name, and each button by its label.
- With text at 200% and the page 320 pixels wide, the labels stay whole.
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves focus to the next button in the group. |
| Shift+Tab | Moves focus to the previous button. |
| EnterorSpace | Presses the focused button. |
Source
The files the button group is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.
Stylesheet
/* smbk-btn-group — Button group. Figma "ButtonGroup" 4163:5325.
* Children are smbk-btn elements. The default type keeps them apart and, in a
* row, wraps onto further lines when it runs out of room. The segmented type
* fuses them into one bar on one line: inner corners are squared, a hairline
* divider separates neighbors, and an outline group carries the border itself
* with ghost buttons inside. */
.smbk-btn-group {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
max-width: 100%;
gap: var(--smbk-space-stack-xs) var(--smbk-space-inline-md);
}
.smbk-btn-group--size-lg {
column-gap: var(--smbk-space-inline-lg);
}
.smbk-btn-group--orientation-vertical {
flex-direction: column;
flex-wrap: nowrap;
align-items: stretch;
gap: var(--smbk-space-stack-sm);
}
.smbk-btn-group--orientation-vertical.smbk-btn-group--size-lg {
gap: var(--smbk-space-stack-md);
}
/* ---- segmented: one bar ---- */
.smbk-btn-group--type-segmented,
.smbk-btn-group--type-segmented.smbk-btn-group--size-lg {
flex-wrap: nowrap;
max-width: none;
gap: 0;
border-radius: var(--smbk-radius-control-md);
}
/* A focused button sits above its neighbors, so the fused edge never covers its ring. */
.smbk-btn-group--type-segmented > .smbk-btn:focus-visible,
.smbk-btn-group--type-segmented > .smbk-dropdown__anchor > .smbk-btn:focus-visible {
position: relative;
z-index: 1;
}
.smbk-btn-group--type-segmented.smbk-btn-group--size-md,
.smbk-btn-group--type-segmented.smbk-btn-group--size-sm {
border-radius: var(--smbk-radius-control-sm);
}
.smbk-btn-group--type-segmented > .smbk-btn {
border-radius: 0;
}
.smbk-btn-group--type-segmented > .smbk-btn:first-child {
border-start-start-radius: inherit;
border-end-start-radius: inherit;
}
.smbk-btn-group--type-segmented > .smbk-btn:last-child {
border-start-end-radius: inherit;
border-end-end-radius: inherit;
}
.smbk-btn-group--orientation-vertical.smbk-btn-group--type-segmented > .smbk-btn:first-child {
border-radius: 0;
border-start-start-radius: inherit;
border-start-end-radius: inherit;
}
.smbk-btn-group--orientation-vertical.smbk-btn-group--type-segmented > .smbk-btn:last-child {
border-radius: 0;
border-end-start-radius: inherit;
border-end-end-radius: inherit;
}
/* Divider between neighbors: the strong border on secondary bars, the subtle border on outline bars, the brand action's own edge on the brand bar. */
.smbk-btn-group--type-segmented > .smbk-btn + .smbk-btn {
border-inline-start: var(--smbk-border-width-hairline) solid var(--smbk-color-border-strong);
}
.smbk-btn-group--orientation-vertical.smbk-btn-group--type-segmented > .smbk-btn + .smbk-btn {
border-inline-start: 0;
border-block-start: var(--smbk-border-width-hairline) solid var(--smbk-color-border-strong);
}
.smbk-btn-group--tone-outline.smbk-btn-group--type-segmented > .smbk-btn + .smbk-btn {
border-inline-start-color: var(--smbk-color-border-subtle);
}
.smbk-btn-group--tone-outline.smbk-btn-group--orientation-vertical.smbk-btn-group--type-segmented > .smbk-btn + .smbk-btn {
border-block-start-color: var(--smbk-color-border-subtle);
}
.smbk-btn-group--tone-brand.smbk-btn-group--type-segmented > .smbk-btn + .smbk-btn {
border-inline-start-color: var(--smbk-color-action-brand-border);
}
.smbk-btn-group--tone-brand.smbk-btn-group--orientation-vertical.smbk-btn-group--type-segmented > .smbk-btn + .smbk-btn {
border-block-start-color: var(--smbk-color-action-brand-border);
}
/* The outline bar draws the border itself; buttons inside are ghost, or outline buttons that give up their own edge. */
.smbk-btn-group--tone-outline.smbk-btn-group--type-segmented > .smbk-btn--tone-outline {
border-color: transparent;
}
.smbk-btn-group--tone-outline.smbk-btn-group--type-segmented > .smbk-btn--tone-outline + .smbk-btn--tone-outline {
border-inline-start-color: var(--smbk-color-border-subtle);
}
.smbk-btn-group--tone-outline.smbk-btn-group--orientation-vertical.smbk-btn-group--type-segmented > .smbk-btn--tone-outline + .smbk-btn--tone-outline {
border-inline-start-color: transparent;
border-block-start-color: var(--smbk-color-border-subtle);
}
/* The bar's edge is drawn inside it, over its buttons' own transparent edges, so it adds nothing: the bar is
as tall as its buttons, the control height of its size. An outline is painted above the buttons, so a
pressed or hovered button's fill never covers it. */
.smbk-btn-group--tone-outline.smbk-btn-group--type-segmented {
outline: var(--smbk-border-width-hairline) solid var(--smbk-color-action-outline-border);
outline-offset: calc(-1 * var(--smbk-border-width-hairline));
}
/* ---- a button that opens a dropdown ----
The dropdown's anchor holds its opener, so the opener is not a child of the bar. In a merged or
segmented row the anchor passes the bar's corners on to its button, and the button draws the same
divider and edge as a button standing in the bar. */
.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > .smbk-dropdown__anchor {
border-radius: 0;
}
.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > .smbk-dropdown__anchor:first-child {
border-start-start-radius: inherit;
border-end-start-radius: inherit;
}
.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > .smbk-dropdown__anchor:last-child {
border-start-end-radius: inherit;
border-end-end-radius: inherit;
}
.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > .smbk-dropdown__anchor > .smbk-btn {
border-radius: inherit;
}
.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > .smbk-btn + .smbk-dropdown__anchor > .smbk-btn {
border-inline-start: var(--smbk-border-width-hairline) solid var(--smbk-color-border-strong);
}
.smbk-btn-group--tone-outline.smbk-btn-group--type-segmented > .smbk-dropdown__anchor > .smbk-btn--tone-outline {
border-color: transparent;
}
.smbk-btn-group--tone-outline.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > .smbk-btn + .smbk-dropdown__anchor > .smbk-btn {
border-inline-start-color: var(--smbk-color-border-subtle);
}
.smbk-btn-group--tone-brand.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > .smbk-btn + .smbk-dropdown__anchor > .smbk-btn {
border-inline-start-color: var(--smbk-color-action-brand-border);
}
/* ---- touch: the buttons stop their press areas halfway to each other ----
Every button takes an invisible press area on a touch screen. In a group, whose buttons stand closer than that and
wrap, each area reaches only halfway into the group's gap, side to side and up and down; in a column, only
halfway to the button above or below. In a segmented bar the buttons touch, so between two of them each keeps its
own edge. Nothing drawn moves. */
@media (pointer: coarse) {
.smbk-btn-group .smbk-btn::after {
inset-inline: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-md) / -2);
inset-block: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-xs) / -2);
}
.smbk-btn-group--size-lg .smbk-btn::after {
inset-inline: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-lg) / -2);
}
.smbk-btn-group--orientation-vertical .smbk-btn::after {
inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
}
.smbk-btn-group--orientation-vertical > * + * .smbk-btn::after,
.smbk-btn-group--orientation-vertical > .smbk-btn + .smbk-btn::after {
inset-block-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-sm) / -2);
}
.smbk-btn-group--orientation-vertical > *:has(+ *) .smbk-btn::after,
.smbk-btn-group--orientation-vertical > .smbk-btn:has(+ *)::after {
inset-block-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-sm) / -2);
}
.smbk-btn-group--orientation-vertical.smbk-btn-group--size-lg > * + * .smbk-btn::after,
.smbk-btn-group--orientation-vertical.smbk-btn-group--size-lg > .smbk-btn + .smbk-btn::after {
inset-block-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-md) / -2);
}
.smbk-btn-group--orientation-vertical.smbk-btn-group--size-lg > *:has(+ *) .smbk-btn::after,
.smbk-btn-group--orientation-vertical.smbk-btn-group--size-lg > .smbk-btn:has(+ *)::after {
inset-block-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-md) / -2);
}
.smbk-btn-group.smbk-btn-group--type-segmented .smbk-btn::after {
inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
}
.smbk-btn-group.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > :not(:first-child)::after,
.smbk-btn-group.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > :not(:first-child) > .smbk-btn::after {
inset-inline-start: 0;
}
.smbk-btn-group.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > :not(:last-child)::after,
.smbk-btn-group.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > :not(:last-child) > .smbk-btn::after {
inset-inline-end: 0;
}
.smbk-btn-group.smbk-btn-group--type-segmented.smbk-btn-group--orientation-vertical > :not(:first-child)::after,
.smbk-btn-group.smbk-btn-group--type-segmented.smbk-btn-group--orientation-vertical > :not(:first-child) > .smbk-btn::after {
inset-block-start: 0;
}
.smbk-btn-group.smbk-btn-group--type-segmented.smbk-btn-group--orientation-vertical > :not(:last-child)::after,
.smbk-btn-group.smbk-btn-group--type-segmented.smbk-btn-group--orientation-vertical > :not(:last-child) > .smbk-btn::after {
inset-block-end: 0;
}
}Tokens it reads
| Token and CSS | Value | Preview | Foundation |
|---|---|---|---|
| space.stack.xs--smbk-space-stack-xs | 4px | Spacing | |
| space.inline.md--smbk-space-inline-md | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| space.inline.lg--smbk-space-inline-lg | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| space.stack.sm--smbk-space-stack-sm | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| space.stack.md--smbk-space-stack-md | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| radius.control.md--smbk-radius-control-md | 6px | Radius | |
| radius.control.sm--smbk-radius-control-sm | 4px | Radius | |
| border-width.hairline--smbk-border-width-hairline | 1px | Border width | |
| color.border.strong--smbk-color-border-strong | light: #9ba5b1dark: #373d43changes with theme | Color | |
| color.border.subtle--smbk-color-border-subtle | light: #c4cdd8dark: #2d3237changes with theme | Color | |
| color.action.brand.border--smbk-color-action-brand-border | #ffffff1a | Color | |
| color.action.outline.border--smbk-color-action-outline-border | light: #c4cdd8dark: #373d43changes with theme | Color | |
| size.target.comfortable--smbk-size-target-comfortable | 44px | Size |
Manifest
{
"name": "button-group",
"block": "smbk-btn-group",
"summary": "Several buttons acting as one control: spaced apart, or segmented into one bar with dividers.",
"adapt": "css",
"axes": {
"type": {
"values": [
"default",
"segmented"
],
"default": "segmented",
"description": "Default keeps buttons apart. Segmented fuses them into one bar with hairline dividers."
},
"tone": {
"values": [
"brand",
"secondary",
"outline"
],
"default": "secondary",
"description": "Which action family the fused bar draws its dividers and border from. Buttons inside an outline bar use the ghost tone."
},
"size": {
"values": [
"lg",
"md",
"sm"
],
"default": "md",
"description": "Matches the size of the buttons inside, md like the button; sets the gap and the bar radius."
},
"orientation": {
"values": [
"horizontal",
"vertical"
],
"default": "horizontal"
}
},
"flags": {},
"parts": [],
"figmaNodeId": "4163:5325"
}