Button group & split

Two shapes under one class. Attached, the segments share edges and read as a single control - use it for a set of mutually exclusive choices. Detached, it is a plain row of related but independent actions. A split button is the same shape with a menu on the end. For a set of choices that has to SUBMIT, see Segmented on the Field page: these are buttons, and buttons carry no value.

Reference

sk-button-group

Direct children must be sk-button. The shared 1px border is collapsed with a negative margin rather than removed, so a hovered or focused segment keeps its whole outline instead of showing a clipped focus ring.

<div class="sk-button-group" role="group" aria-label="View">
  <button class="sk-button" data-variant="neutral" aria-pressed="true">Grid</button>
  <button class="sk-button" data-variant="neutral" aria-pressed="false">List</button>
</div>
AttributeTypeDefaultNotes
data-attached"false"attachedOmit for the segmented shape. Set to "false" for a spaced row of independent actions.
data-orientation"vertical"horizontalStacks the segments and moves the rounded corners to top and bottom.
role"group""group"Set by the binding.
aria-labelstring-Name the choice, not the options. Without it a segmented control is announced as a bare list of buttons.

sk-button[aria-pressed]

The selected segment. Driven by aria-pressed so the visual state and the announced state are the same attribute - there is no separate .is-active class to fall out of sync.

<button class="sk-button" aria-pressed="true">Selected</button>

sk-split

One primary action with its variants behind a menu. Built on the group's shape - same joined corners, same collapsed border, same z-index on focus so a ring is never clipped - and adds one thing: the seam between two SOLID buttons, drawn as an inset rule in currentColor at low alpha so it works on every variant. Without it the pair looks like one very wide button that mysteriously responds to a click on its right-hand quarter.

<div class="sk-split">
  <button class="sk-button">Deploy</button>
  <button class="sk-button" popovertarget="deploy-menu"
          aria-label="More deploy options">…</button>
</div>
<div class="sk-menu" id="deploy-menu" popover>…</div>
AttributeTypeDefaultNotes
aria-labelstring-On the toggle, and it matters more here than almost anywhere else: a primary button followed by an unnamed one is announced as "Deploy, button, button". Name it after what it opens - never "Menu" or "Toggle".
> .sk-button:last-child--The toggle is matched structurally rather than by a class: a binding cannot add a class through an attribute pass-through without emitting a second class attribute, which the browser resolves by keeping the first and dropping the rest.

Examples

Split button

The common action stays one click away while its rarer siblings stop competing for room in the toolbar. Tab to the second half: it has a name of its own, because "Deploy, button, button" tells the reader nothing.


Show markup
<div class="sk-flex sk-flex-wrap sk-items-center sk-gap-4">
  <div class="sk-split">
    <button class="sk-button" type="button" data-variant="solid" data-size="md">
      Deploy
    </button>
    <button class="sk-button" type="button" data-variant="solid" data-size="md" aria-label="More deploy options" popovertarget="split-deploy">
      <svg class="sk-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
        <path d="m6 9 6 6 6-6">
        </path>
      </svg>
    </button>
  </div>
  <div class="sk-menu" id="split-deploy" popover aria-label="Deploy options">
    <button type="button" class="sk-menu-item">
      Deploy to production
      <span class="sk-menu-item-detail">
        <kbd class="sk-kbd">
          Ctrl
        </kbd>
        <kbd class="sk-kbd">
          D
        </kbd>
      </span>
    </button>
    <button type="button" class="sk-menu-item">
      Deploy to staging
    </button>
    <button type="button" class="sk-menu-item">
      Deploy a specific build&hellip;
    </button>
    <hr class="sk-menu-separator">
    <button type="button" class="sk-menu-item" data-status="danger">
      Roll back
    </button>
  </div>
  <div class="sk-split">
    <button class="sk-button" type="button" data-variant="neutral" data-size="sm">
      Export CSV
    </button>
    <button class="sk-button" type="button" data-variant="neutral" data-size="sm" aria-label="More export formats" popovertarget="split-export">
      <svg class="sk-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
        <path d="m6 9 6 6 6-6">
        </path>
      </svg>
    </button>
  </div>
  <div class="sk-menu" id="split-export" popover aria-label="Export formats">
    <button type="button" class="sk-menu-item">
      Export JSON
    </button>
    <button type="button" class="sk-menu-item">
      Export XLSX
    </button>
  </div>
</div>

Segmented control

The attached default. One segment is pressed; the group carries the accessible name.

Show markup
<div class="sk-flex sk-flex-col sk-gap-4">
  <div class="sk-button-group" role="group" aria-label="View">
    <button class="sk-button" type="button" data-variant="neutral" data-size="md" aria-pressed="true">
      Grid
    </button>
    <button class="sk-button" type="button" data-variant="neutral" data-size="md" aria-pressed="false">
      List
    </button>
    <button class="sk-button" type="button" data-variant="neutral" data-size="md" aria-pressed="false">
      Table
    </button>
  </div>
  <div class="sk-button-group" role="group" aria-label="Range">
    <button class="sk-button" type="button" data-variant="neutral" data-size="md" aria-pressed="false">
      Day
    </button>
    <button class="sk-button" type="button" data-variant="neutral" data-size="md" aria-pressed="true">
      Week
    </button>
    <button class="sk-button" type="button" data-variant="neutral" data-size="md" aria-pressed="false">
      Month
    </button>
    <button class="sk-button" type="button" data-variant="neutral" data-size="md" aria-pressed="false">
      Year
    </button>
  </div>
</div>

Sizes

Corner radius follows the size variant rather than being hardcoded, so a small group keeps the smaller corner.

Show markup
<div class="sk-flex sk-flex-col sk-items-start sk-gap-4">
  <div class="sk-button-group" role="group" aria-label="Alignment">
    <button class="sk-button" type="button" data-variant="neutral" data-size="sm" aria-pressed="true">
      Left
    </button>
    <button class="sk-button" type="button" data-variant="neutral" data-size="sm" aria-pressed="false">
      Centre
    </button>
    <button class="sk-button" type="button" data-variant="neutral" data-size="sm" aria-pressed="false">
      Right
    </button>
  </div>
  <div class="sk-button-group" role="group" aria-label="Alignment">
    <button class="sk-button" type="button" data-variant="neutral" data-size="md" aria-pressed="true">
      Left
    </button>
    <button class="sk-button" type="button" data-variant="neutral" data-size="md" aria-pressed="false">
      Centre
    </button>
    <button class="sk-button" type="button" data-variant="neutral" data-size="md" aria-pressed="false">
      Right
    </button>
  </div>
  <div class="sk-button-group" role="group" aria-label="Alignment">
    <button class="sk-button" type="button" data-variant="neutral" data-size="lg" aria-pressed="true">
      Left
    </button>
    <button class="sk-button" type="button" data-variant="neutral" data-size="lg" aria-pressed="false">
      Centre
    </button>
    <button class="sk-button" type="button" data-variant="neutral" data-size="lg" aria-pressed="false">
      Right
    </button>
  </div>
</div>

Detached

For a form's action row, where the buttons are independent rather than mutually exclusive.

Show markup
<div class="sk-button-group" role="group" data-attached="false">
  <button class="sk-button" type="submit" data-variant="solid" data-size="md">
    Save
  </button>
  <button class="sk-button" type="button" data-variant="quiet" data-size="md">
    Cancel
  </button>
</div>

Vertical

Show markup
<div class="sk-button-group" role="group" aria-label="Sort" data-orientation="vertical">
  <button class="sk-button" type="button" data-variant="neutral" data-size="md" aria-pressed="true">
    Newest first
  </button>
  <button class="sk-button" type="button" data-variant="neutral" data-size="md" aria-pressed="false">
    Oldest first
  </button>
  <button class="sk-button" type="button" data-variant="neutral" data-size="md" aria-pressed="false">
    Alphabetical
  </button>
</div>

Mixed variants

Segments keep their own variant, so a destructive option can sit in an otherwise neutral group.

Show markup
<div class="sk-button-group" role="group" aria-label="Record actions">
  <button class="sk-button" type="button" data-variant="neutral" data-size="md">
    Edit
  </button>
  <button class="sk-button" type="button" data-variant="neutral" data-size="md">
    Duplicate
  </button>
  <button class="sk-button" type="button" data-variant="danger" data-size="md">
    Delete
  </button>
</div>