Toggle group

A pill container that hosts a row of toggles pressed inside its padded interior.

For a single press button, see Toggle. For purely visual button grouping with no state, see Button group.

Single-select (segmented control)

Use role="radiogroup" on the wrapper and role="radio" with aria-checked on each member. The CSS hook is data-state="active" on the current member. Add data-stisla-toggle-group to wire the click and arrow-key contract. The type autodetects from role="radiogroup".

<div class="toggle-group" data-stisla-toggle-group role="radiogroup" aria-label="Text alignment">
  <button type="button" class="toggle toggle--icon-only" role="radio" aria-checked="false" aria-label="Align left" data-value="left">
    <i data-lucide="align-left"></i>
  </button>
  <button type="button" class="toggle toggle--icon-only" role="radio" data-state="active" aria-checked="true" aria-label="Align center" data-value="center">
    <i data-lucide="align-center"></i>
  </button>
  <button type="button" class="toggle toggle--icon-only" role="radio" aria-checked="false" aria-label="Align right" data-value="right">
    <i data-lucide="align-right"></i>
  </button>
</div>

Multi-select

Each member is an independent press toggle. Use role="group" on the wrapper and aria-pressed on each member. The type autodetects to multiple when no radio role is present; arrows move focus, Space or Enter commits the press.

<div class="toggle-group" data-stisla-toggle-group role="group" aria-label="Text style">
  <button type="button" class="toggle toggle--icon-only" aria-pressed="true" aria-label="Bold" data-value="bold">
    <i data-lucide="bold"></i>
  </button>
  <button type="button" class="toggle toggle--icon-only" aria-pressed="false" aria-label="Italic" data-value="italic">
    <i data-lucide="italic"></i>
  </button>
  <button type="button" class="toggle toggle--icon-only" aria-pressed="true" aria-label="Underline" data-value="underline">
    <i data-lucide="underline"></i>
  </button>
</div>

With text labels

Members can carry text instead of (or alongside) icons. The group's intrinsic width grows to fit.

<div class="toggle-group" data-stisla-toggle-group role="radiogroup" aria-label="Calendar view">
  <button type="button" class="toggle" role="radio" data-state="active" aria-checked="true" data-value="day">Day</button>
  <button type="button" class="toggle" role="radio" aria-checked="false" data-value="week">Week</button>
  <button type="button" class="toggle" role="radio" aria-checked="false" data-value="month">Month</button>
</div>

Icon + label

Mix icons with text by placing an <i> inside the .toggle button next to the label.

<div class="toggle-group" data-stisla-toggle-group role="radiogroup" aria-label="View mode">
  <button type="button" class="toggle" role="radio" data-state="active" aria-checked="true" data-value="list">
    <i data-lucide="list"></i>
    List
  </button>
  <button type="button" class="toggle" role="radio" aria-checked="false" data-value="grid">
    <i data-lucide="layout-grid"></i>
    Grid
  </button>
  <button type="button" class="toggle" role="radio" aria-checked="false" data-value="kanban">
    <i data-lucide="columns-3"></i>
    Kanban
  </button>
</div>

Form data, radio set

For single-select that needs to submit with a form, use native radios. The hidden <input class="toggle-input" type="radio"> drives the paired <label class="toggle">; the browser handles single-select natively. No JS required.

<div class="toggle-group">
  <input type="radio" class="toggle-input" name="planRange" id="planDay" value="day" checked>
  <label class="toggle" for="planDay">Day</label>
  <input type="radio" class="toggle-input" name="planRange" id="planWeek" value="week">
  <label class="toggle" for="planWeek">Week</label>
  <input type="radio" class="toggle-input" name="planRange" id="planMonth" value="month">
  <label class="toggle" for="planMonth">Month</label>
</div>

Form data, checkbox set

For multi-select form data, use native checkboxes the same way. Multiple labels can be active at once.

<div class="toggle-group">
  <input type="checkbox" class="toggle-input" name="tools" id="toolBold" value="bold" checked>
  <label class="toggle toggle--icon-only" for="toolBold" aria-label="Bold">
    <i data-lucide="bold"></i>
  </label>
  <input type="checkbox" class="toggle-input" name="tools" id="toolItalic" value="italic">
  <label class="toggle toggle--icon-only" for="toolItalic" aria-label="Italic">
    <i data-lucide="italic"></i>
  </label>
  <input type="checkbox" class="toggle-input" name="tools" id="toolUnderline" value="underline" checked>
  <label class="toggle toggle--icon-only" for="toolUnderline" aria-label="Underline">
    <i data-lucide="underline"></i>
  </label>
</div>

Sizes

Add .toggle-group--sm or .toggle-group--lg. Outer height shrinks to 28 / grows to 44 px under default density; the inner toggle height re-derives from the new container values automatically (concentric corners per V3.md §3.4).

<div class="d-flex flex-column gap-2 align-items-start">
  <div class="toggle-group toggle-group--sm" data-stisla-toggle-group role="radiogroup" aria-label="Small">
    <button type="button" class="toggle" role="radio" data-state="active" aria-checked="true">Day</button>
    <button type="button" class="toggle" role="radio" aria-checked="false">Week</button>
    <button type="button" class="toggle" role="radio" aria-checked="false">Month</button>
  </div>
  <div class="toggle-group" data-stisla-toggle-group role="radiogroup" aria-label="Default">
    <button type="button" class="toggle" role="radio" data-state="active" aria-checked="true">Day</button>
    <button type="button" class="toggle" role="radio" aria-checked="false">Week</button>
    <button type="button" class="toggle" role="radio" aria-checked="false">Month</button>
  </div>
  <div class="toggle-group toggle-group--lg" data-stisla-toggle-group role="radiogroup" aria-label="Large">
    <button type="button" class="toggle" role="radio" data-state="active" aria-checked="true">Day</button>
    <button type="button" class="toggle" role="radio" aria-checked="false">Week</button>
    <button type="button" class="toggle" role="radio" aria-checked="false">Month</button>
  </div>
</div>

Vertical

Add .toggle-group--vertical to stack members. The container is block-level so it fills its parent's width. Wrap in a sized container (sidebar shell, settings panel) to constrain. Inner toggles keep their standalone default height; content left-aligns as a menu list.

<div style="max-width: 240px;">
  <div class="toggle-group toggle-group--vertical" data-stisla-toggle-group role="radiogroup" aria-label="Sidebar view">
    <button type="button" class="toggle" role="radio" data-state="active" aria-checked="true" data-value="inbox">
      <i data-lucide="inbox"></i>
      Inbox
    </button>
    <button type="button" class="toggle" role="radio" aria-checked="false" data-value="archive">
      <i data-lucide="archive"></i>
      Archive
    </button>
    <button type="button" class="toggle" role="radio" aria-checked="false" data-value="trash">
      <i data-lucide="trash-2"></i>
      Trash
    </button>
  </div>
</div>

Keyboard

Roving tabindex keeps one member in the tab order at a time, so Tab leaves the group naturally. Arrow keys move focus along the group's orientation. In single-select mode focus is selection (WAI-ARIA radio-group); multi-select decouples them.

Key Single-select Multi-select
/ Focus next enabled member, auto-select. Focus next enabled member.
/ Focus previous enabled member, auto-select. Focus previous enabled member.
Home Focus first enabled, auto-select. Focus first enabled member.
End Focus last enabled, auto-select. Focus last enabled member.
Space / Enter Select focused (no-op if already selected). Flip aria-pressed on focused member.
Tab Leaves the group. Only the tabbable member is in the tab order.

Customization

Six variables retune .toggle-group. Child .toggle vars (height, radius, bg, border) re-derive from the container values inside member scope. See Toggle for the child surface.

Variable Default Use
--toggle-group-radius var(--st-toggle-group-radius, var(--st-radius)) Outer corner radius. Inner toggle radius computes as outer - padding (concentric).
--toggle-group-height 2.25rem Outer raw height (density multiplies once). Inner toggle fills via flex stretch, so height resolves to outer − 2 × border − 2 × padding via the box model.
--toggle-group-padding 0.125rem Padding around the children. Subtracts from the inner toggle's effective height and from the concentric inner radius.
--toggle-group-gap 0.125rem Space between adjacent toggles. Lets multi-active members breathe without border collisions.
--toggle-group-bg transparent Container fill. Set to var(--st-surface-2) for a tinted pill.
--toggle-group-border-color var(--st-border) Outer rim color.