Button group
A row of action buttons that share chrome so they read as a single control.
For press toggles, segmented controls, and toggle-style checkbox / radio groups, see Toggle and Toggle group. .btn-group is purely visual grouping for action buttons, with no state hooks and no JS.
Basic
Wrap two or more .btns in .btn-group with role="group" and an aria-label. Outer corners round, inner corners square, adjacent borders dedupe into a single seam.
<div class="btn-group" role="group" aria-label="Basic example">
<button type="button" class="btn btn--primary">Left</button>
<button type="button" class="btn btn--primary">Middle</button>
<button type="button" class="btn btn--primary">Right</button>
</div>Outline
Outline members work the same way, with adjacent borders deduping at the seam.
<div class="btn-group" role="group" aria-label="Outline example">
<button type="button" class="btn btn--outline btn--neutral">Left</button>
<button type="button" class="btn btn--outline btn--neutral">Middle</button>
<button type="button" class="btn btn--outline btn--neutral">Right</button>
</div>Mixed
A loud action paired with a quiet alternative. Same pattern as the standalone primary plus outline-neutral pairing, locked together as one chip.
<div class="btn-group" role="group" aria-label="Mixed example">
<button type="button" class="btn btn--primary">Publish</button>
<button type="button" class="btn btn--outline btn--neutral">Save draft</button>
</div>Sizes
Add .btn-group--sm or .btn-group--lg on the wrapper. The modifier retunes child --btn-* vars so every member shrinks or grows at once, matching the standalone .btn--sm / .btn--lg cadence (28 / 36 / 44 px under default density).
<div class="d-flex flex-column gap-2 align-items-start">
<div class="btn-group btn-group--sm" role="group" aria-label="Small">
<button type="button" class="btn btn--outline btn--neutral">Left</button>
<button type="button" class="btn btn--outline btn--neutral">Middle</button>
<button type="button" class="btn btn--outline btn--neutral">Right</button>
</div>
<div class="btn-group" role="group" aria-label="Default">
<button type="button" class="btn btn--outline btn--neutral">Left</button>
<button type="button" class="btn btn--outline btn--neutral">Middle</button>
<button type="button" class="btn btn--outline btn--neutral">Right</button>
</div>
<div class="btn-group btn-group--lg" role="group" aria-label="Large">
<button type="button" class="btn btn--outline btn--neutral">Left</button>
<button type="button" class="btn btn--outline btn--neutral">Middle</button>
<button type="button" class="btn btn--outline btn--neutral">Right</button>
</div>
</div>With icons
Icons compose the same as in standalone buttons. .btn--icon-only gives a square slot.
<div class="btn-group" role="group" aria-label="Format">
<button type="button" class="btn btn--outline btn--neutral btn--icon-only" aria-label="Cut">
<i data-lucide="scissors"></i>
</button>
<button type="button" class="btn btn--outline btn--neutral btn--icon-only" aria-label="Copy">
<i data-lucide="copy"></i>
</button>
<button type="button" class="btn btn--outline btn--neutral btn--icon-only" aria-label="Paste">
<i data-lucide="clipboard"></i>
</button>
</div>Split button
Pair a primary action with a caret-only trigger. The trigger reuses .btn--icon-only with a chevron, so no split-specific class ships. Dropdown attach behavior lands with the Dropdown component in a later step.
<div class="btn-group" role="group" aria-label="Save">
<button type="button" class="btn btn--primary">Save</button>
<button type="button" class="btn btn--primary btn--icon-only" aria-label="More save options" aria-haspopup="menu" aria-expanded="false">
<i data-lucide="chevron-down"></i>
</button>
</div>Vertical
Swap .btn-group for .btn-group--vertical to stack the members. Outer corners round on the top and bottom instead of left and right.
<div class="btn-group--vertical" role="group" aria-label="Vertical example">
<button type="button" class="btn btn--primary">Top</button>
<button type="button" class="btn btn--primary">Middle</button>
<button type="button" class="btn btn--primary">Bottom</button>
</div>Toolbar
Combine multiple groups under .btn-toolbar. The toolbar carries a default 0.5rem gap between groups so they breathe without inline utility classes.
<div class="btn-toolbar" role="toolbar" aria-label="Toolbar with button groups">
<div class="btn-group" role="group" aria-label="First group">
<button type="button" class="btn btn--outline btn--neutral">1</button>
<button type="button" class="btn btn--outline btn--neutral">2</button>
<button type="button" class="btn btn--outline btn--neutral">3</button>
<button type="button" class="btn btn--outline btn--neutral">4</button>
</div>
<div class="btn-group" role="group" aria-label="Second group">
<button type="button" class="btn btn--outline btn--neutral">5</button>
<button type="button" class="btn btn--outline btn--neutral">6</button>
<button type="button" class="btn btn--outline btn--neutral">7</button>
</div>
<div class="btn-group" role="group" aria-label="Third group">
<button type="button" class="btn btn--outline btn--neutral">8</button>
</div>
</div>Customization
Two variables retune .btn-group + .btn-toolbar. Sizes retune the child --btn-* vars in modifier scope rather than introducing per-size vars on the group, the same model as .btn--sm / .btn--lg. Override on the wrapper, a parent scope, or :root; the cascade scopes the change.
| Variable | Default | Use |
|---|---|---|
--btn-group-radius |
var(--st-btn-radius, var(--st-radius)) | Outer corner radius. Inner corners stay square. --sm / --lg retune this in modifier scope. |
--btn-toolbar-gap |
0.5rem | Space between groups inside a .btn-toolbar. |
The group's chrome (members' borders, fills, hover and active paint) lives on the child .btn. To recolor a cluster, set --btn-bg / --btn-tone on the members. Don't set them on the wrapper. See the button page for the full .btn variable surface.