Swap

A CSS-first state indicator with checkbox and application-controlled contracts

Swap

Swap replaces one visual state indicator with another while keeping a stable control footprint. It does not manage application state and does not replace Switch, Toggle Button, or Theme Controller.

@import "@duskmoon-dev/core/components/swap";

Checkbox-driven state

The native checkbox is authoritative. Give the input a stable accessible name; keep icons decorative and do not nest interactive controls inside either slot.

Menu and close indicator

Rotating light and dark indicator

Disabling .swap-input applies the disabled presentation and prevents native activation.

Application-controlled button

For a button, aria-pressed is authoritative. JavaScript must update it; CSS never updates ARIA.

Controlled mute button

Changing the accessible name here describes the action the button will perform. For a stable preference label, use a labelled group and expose the current state separately.

Presentation-only state

.swap-active forces the on slot for a noninteractive presentation. It takes visual precedence over checkbox or aria-pressed state, so do not use it on an interactive control where it could contradict semantic state.

<span class="swap swap-active" aria-label="Synchronization complete">
  <span class="swap-off" aria-hidden="true">…</span>
  <span class="swap-on" aria-hidden="true">✓</span>
</span>

API

Class Purpose
.swap Stable overlay grid for both state slots
.swap-input Native checkbox state source
.swap-off Content visible for unchecked or aria-pressed="false" state
.swap-on Content visible for checked or aria-pressed="true" state
.swap-rotate Rotation transition modifier
.swap-active Presentation-only forced-on state

Inactive slots are hidden from pointer interaction and visibility. Keep slots limited to indicators or labels: nested links, buttons, inputs, and panels create misleading focus and accessibility behavior. Transitions are removed for reduced-motion users.