Button group

Joins related buttons into one control.

Orientation

  • horizontaldefault

  • vertical

Content

  • 2 of 14

    textdefault

  • separator

  • split

States

  • rest

  • hover

  • held

  • focus-visible

  • disabled

Usage

When to use it
Use
Two or three actions of one kind that belong together: previous and next, zoom out and in, pause and resume.
Use
A value between the buttons that change it (ButtonGroupText).
Use
A row of quiet ghost actions split into halves (ButtonGroupSeparator).
Use
One action with a few close alternatives behind a caret (ButtonGroupMenu, the split button).
Not for
One view of several is chosen. Use Segmented control.
Not for
Unrelated actions that only share a row; space them 8px apart. Use Button.

Rules

7 rules
Must
Make every member variant="secondary" or "ghost"; put the one slab beside the group, not in it. Because the group is one control, and a yellow cell joined to edged ones reads as a chosen segment rather than the next step.
Must
Give every member the same size and variant. Because mixed heights break the seams, and mixed surfaces make one cell look chosen.
Must
Name the group with aria-label when its members do not say what they act on. Because "Previous, Next" means nothing without the thing they page through.
Never
Show a chosen member by giving it the slab or the selection wash; that is a segmented control. Because every member of a group is an action, and a member styled as chosen reads as a view switcher with no radio semantics.
Never
Add a gap or margins between members. Because the members share one 1px seam, and a gap turns the one control into loose buttons with doubled edges.
Should
Keep a group to two or three members, plus a ButtonGroupText. Because a long joined row is hard to aim at and reads as a segmented control.
Should
Use ButtonGroupText for the value the buttons change, and announce it with aria-live="polite".

State order

5 states, highest first
1
disabled
2
held
3
focus-visible
4
hover
5
rest

Accessibility

4 checks
role="group" named by aria-label; each member is its own button in the tab order.
Icon members need aria-label; ButtonGroupText with aria-live announces the new value.
The focused member rises over its neighbours and draws its 2px ring on its own edge, so the ring shows whole.
ButtonGroupText is text-2 on the page, 7.8:1 and 10.2:1.

Keyboard

2 keys
Tab
Moves from member to member.
Enter, Space
Presses the focused member.

Edge cases

7 edge cases
Member held
The fill steps darker and nothing scales, so the seams never open.
Member disabled
Drops to the disabled label in place; the seams stay.
Group of groups
Groups nested in a ButtonGroup sit 8px apart, each its own ruled row.
Right to left
Members run right to left and the seams follow.
Dark theme
Members sit on the near-black page; their --line edges make the seams.
Split button
The caret's menu lines up under the whole group and is at least as wide; the caret turns at once while it is open.
Split button while running
Pass the main action's pending to ButtonGroupMenu, so the alternatives lock with it.

API

6 props
orientation
'horizontal' | 'vertical' default 'horizontal'
aria-label
string
ButtonGroupText
div (render)
ButtonGroupMenu label, size, variant
string, sm | default | lg, secondary | ghost default 'default', 'secondary'
ButtonGroupMenu pending
boolean
ButtonGroupSeparator orientation
'vertical' | 'horizontal' default 'vertical'

Tokens

7 tokens
--line
#e2dfd5
--secondary-bg
#f8f7f3
--text-2
#55524a
--control
32px
--z-raised
10
--type-label
400 13px/16px var(--font-mono)
--focus
#7a5d00

Install

For your coding agent

GitHub

Please star me pookie

198

A new design system every week

Leave your email before you copy the prompt.