Malevich

Elements / Interactive/

Toggle

Button that retains pressed state via aria-pressed.

Toggle

A button that retains a pressed state. Used for inline on/off controls that visually resemble buttons more than switches — toolbar tools, filter chips, segmented options.

Distinct from:

When to use

For groups where exactly one option is selected, use a Tab. For multiple-select chip groups, use multiple Toggles + custom UX.

Variants

Variant Class Use for
Default .toggle Bordered, ink-strong when pressed
Accent .toggle.-accent Accent fill when pressed
Ghost .toggle.-ghost Borderless
Small .toggle.-s Compact (caption typography)
Large .toggle.-l Larger (body-regular typography)

Anatomy

<button type="button" class="toggle" aria-pressed="false">Bold</button>
<button type="button" class="toggle" aria-pressed="true">Italic</button>

<!-- Variants -->
<button type="button" class="toggle -accent" aria-pressed="true">Pinned</button>
<button type="button" class="toggle -ghost" aria-pressed="false">Mute</button>

<!-- Sizes -->
<button type="button" class="toggle -s" aria-pressed="false">Filter</button>
<button type="button" class="toggle -l" aria-pressed="false">Section A</button>

The component's runtime (initToggle, auto-registered) flips aria-pressed between "true" and "false" on click and emits a malevich:toggle CustomEvent (bubbles).

button.addEventListener("malevich:toggle", (e) => {
  console.log("now pressed?", e.detail.pressed);
});

Tokens used

From semantic tier

Accessibility

The component is a real <button type="button">:

Setting aria-pressed to "false" (not omitting it) communicates "this button is a toggle, currently not pressed." Assistive tech distinguishes "button" from "toggle button" based on the presence of aria-pressed.

The runtime auto-populates aria-pressed="false" if the attribute is missing, so authors don't have to remember.

Edge cases

Do

Don't