Button-group
Joins Buttons or Toggles into a connected row with shared borders. Used for filter sets, view toggles, segmented controls. CSS-only.
When to use
- A small set of mutually-related actions or filters.
- A segmented choice where presenting them as one connected unit clarifies the relationship.
If exactly one option must be selected at a time, prefer Tabs. If
items can be independently toggled, use Toggles inside a Button-group.
Variants
| Variant | Class | Use for |
|---|---|---|
| Default | .button-group |
Horizontal row |
| Vertical | .button-group.-vertical |
Stacked column |
Anatomy
<!-- Group of buttons -->
<div class="button-group" role="group" aria-label="Filter by status">
<button class="button -secondary">All</button>
<button class="button -secondary">Active</button>
<button class="button -secondary">Archived</button>
</div>
<!-- Group of toggles (multi-select chips) -->
<div class="button-group" role="group" aria-label="Text formatting">
<button class="toggle" aria-pressed="false">B</button>
<button class="toggle" aria-pressed="true">I</button>
<button class="toggle" aria-pressed="false">U</button>
</div>
<!-- Vertical -->
<div class="button-group -vertical" role="group" aria-label="View">
<button class="button -secondary">List</button>
<button class="button -secondary">Grid</button>
<button class="button -secondary">Cards</button>
</div>
The component is purely a layout wrapper — children remain real
<button> elements with all their semantics.
Tokens used
--radius-button— outer corner radius--border-width-hairline— used for negative-margin overlap
Accessibility
- Always wrap in
role="group"witharia-labelso screen readers announce the group's purpose. - Children remain real
<button>elements — keyboard, focus, and press semantics are unchanged. - The group does not implement roving tab index. Tab/Shift+Tab moves
through each button individually (matching native button behavior).
If you want arrow-key navigation, use
Tabsinstead.
Edge cases
- Mixed content (button + toggle): allowed. Borders and corners handle both via the same selectors.
- Vertical with long labels: the column stretches to the widest child. Labels do not truncate by default.
- Pressed state in a multi-select toggle group: pressed children
rise above neighbors via
z-index: 1so the pressed border isn't obscured by the adjacent border.
Do
- Use real
<button>children, not styled divs. - Provide
role="group"andaria-label. - Keep the group size small — 2-5 items is the sweet spot.
Don't
- Don't nest Button-groups.
- Don't mix sizes (button.-s + button.-l) within one group. Heights won't align.