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.