Button group
Joins related buttons into one control.
Orientation
horizontaldefault
vertical
Split
menudefault
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, fewer and more, zoom out and in.
- 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, a 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 ink action beside the group, not in it. Because the group is one control, and an ink cell in a flat pill reads as a second lead next to the real one.
- Must
- Give every member the same size and variant. Because mixed heights break the seams, and mixed keys 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 making it ink or pressed-looking; 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.
- Must
- Make a split button with ButtonGroupMenu as the last member, matching the main action's variant and size. Because a separate DropdownMenu beside the group opens under the caret alone and reads as a second control, and a mismatched caret breaks the seam.
- Should
- Keep a group to two or three members, plus a ButtonGroupText. Because a long joined pill 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
3 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 so its 2px ink ring shows whole.
Keyboard
2 keys
- Tab
- Moves from member to member.
- Enter, Space
- Presses the focused member.
Edge cases
7 edge cases
- Member held
- No press scale, so the seams never open.
- Member disabled
- Turns flat grey in place; the seams stay.
- Group of groups
- Groups nested in a ButtonGroup sit 8px apart, each its own pill.
- Split button open
- The menu opens under the whole group, at least as wide as it, aligned to its end.
- Split button while the action runs
- Pass pending to ButtonGroupMenu; the caret locks so a second variant cannot start.
- Right to left
- The rounded ends follow the reading direction.
- Dark theme
- Paper members turn near-black; their rings make the seams.
API
6 props
orientation'horizontal' | 'vertical'default'horizontal'aria-labelstringButtonGroupTextdiv (render)ButtonGroupMenu label, variant, sizestring, 'secondary' | 'ghost', 'sm' | 'default' | 'lg'default, 'secondary', 'default'ButtonGroupMenu pending, alignboolean, 'start' | 'end'defaultfalse, 'end'ButtonGroupSeparator orientation'vertical' | 'horizontal'default'vertical'
Tokens
9 tokens
--flat-ring- var(--line-faint)
--flat-bg- var(--paper)
--line- rgb(5 5 5 / 0.10)
--text-2- rgb(5 5 5 / 0.60)
--radius-pill- 999px
--radius-overlay- 10px
--control- 32px
--z-raised- 10
--type-label- 500 13px/1.35 var(--font-sans)