Skip to the page

MCSS-Lite 0.4.1

GitHub

Toggle c-toggle

BetaComponent · since 0.4.0

An on/off switch built on a native checkbox with role="switch": the setting applies as soon as it changes.

Pick this part

Pick c-toggle for

  • A setting that takes effect immediately, with no save or submit button: notifications, dark mode, a feature flag.
  • A system state people expect to flip back and forth.

Pick another part for

  • A choice inside a form that is saved on submit: use c-checkbox.
  • Choosing between two named options that are not on/off, such as "Monthly" and "Yearly": use c-radio.
  • Triggering an action, such as "Export": use c-button.

Build it

  1. Start with <div> and add class="c-toggle".
  2. Add the required parts: c-toggle__input, c-toggle__label.
  3. Add the parts you need: c-toggle__hint.
  4. Set state on the native control, never with data-state: :checked, :disabled.
  5. Add data-state="loading" when the change is being saved. Set aria-busy="true" on the c-toggle and disable the input until the save finishes.

Applies straight away.

Show markup 15 lines
<div class="l-stack l-stack--sm">
  <div class="c-toggle">
    <input class="c-toggle__input" type="checkbox" role="switch" id="setting-email" checked aria-describedby="setting-email-hint">
    <label class="c-toggle__label" for="setting-email">Email notifications</label>
    <p class="c-toggle__hint" id="setting-email-hint">Applies straight away.</p>
  </div>
  <div class="c-toggle">
    <input class="c-toggle__input" type="checkbox" role="switch" id="setting-digest">
    <label class="c-toggle__label" for="setting-digest">Weekly digest</label>
  </div>
  <div class="c-toggle" data-state="loading" aria-busy="true">
    <input class="c-toggle__input" type="checkbox" role="switch" id="setting-beta" checked disabled>
    <label class="c-toggle__label" for="setting-beta">Beta features</label>
  </div>
</div>

Check your build

Each piece that doesn't fit was run through mcss-lite validate when this page was built. The output below is real.

Fits

Build the switch on a real checkbox with role="switch" and a fixed noun label.

Applies straight away.

Show markup 5 lines
<div class="c-toggle">
  <input class="c-toggle__input" type="checkbox" role="switch" id="notify" checked aria-describedby="notify-hint">
  <label class="c-toggle__label" for="notify">Email notifications</label>
  <p class="c-toggle__hint" id="notify-hint">Applies straight away.</p>
</div>

validate: 0 issues

Doesn't fit

Don't fake the on state with data-state: the control has to carry it.

<div class="c-toggle" data-state="checked">
  <input class="c-toggle__input" type="checkbox" role="switch" id="notify-fake">
  <label class="c-toggle__label" for="notify-fake">Email notifications</label>
</div>

validate

  • error "checked" is a native state of c-toggle: don't use data-state. Use the checked attribute; role="switch" makes screen readers say on and off. [invalid-state]

Fits

While a change saves, mark the switch busy and disable the input.

Show markup 4 lines
<div class="c-toggle" data-state="loading" aria-busy="true">
  <input class="c-toggle__input" type="checkbox" role="switch" id="beta" checked disabled>
  <label class="c-toggle__label" for="beta">Beta features</label>
</div>

validate: 0 issues

Doesn't fit

Don't show the loading stripes without aria-busy; screen readers can't tell the save is still running.

<div class="c-toggle" data-state="loading">
  <input class="c-toggle__input" type="checkbox" role="switch" id="beta-x" checked disabled>
  <label class="c-toggle__label" for="beta-x">Beta features</label>
</div>

validate

  • warning data-state="loading" also needs aria-busy="true". [state-pair]

Spec sheet

Apply to <div>

Elements
ClassOnWhat it does
c-toggle__input required<input>The native control: <input type="checkbox" role="switch">. Carries checked and disabled.
c-toggle__label required<label>The name of the setting, linked with for/id. It stays the same when the switch flips.
c-toggle__hint<p> <div> <span>Optional one-line hint under the label, linked with aria-describedby.
States
StateMeaningPair it with
:checkedOn: the thumb slides to the end on an ink track and shows a tick.Use the checked attribute; role="switch" makes screen readers say on and off.
:disabledCannot be changed. Dashed track, ghosted label.Use the disabled attribute, and say nearby why it is unavailable.
data-state="loading"The change is being saved. The track turns neutral with moving stripes.Set aria-busy="true" on the c-toggle and disable the input until the save finishes.
Show tokens 6
Tokens
TokenValue (light / dark)
--toggle-track-off#e9ecef / #27436f
--toggle-track-on#1a1d20 / #e6edf4
--toggle-thumb#ffffff / #14294d
--toggle-border#1a1d20 / #cfdae5
--toggle-mark#1a1d20 / #e6edf4
--color-action-loading-stripergb(0 0 0 / 0.14) / rgb(0 0 0 / 0.22)

Accessibility

  • Use <input type="checkbox" role="switch">: screen readers that know switches say "on" and "off", and the rest still say "checked".
  • The thumb shows a tick when on and stays empty when off, so state never relies on position or color alone.
  • Movement stops under prefers-reduced-motion.
Keyboard
KeyWhat it does
TabMoves focus to the switch.
SpaceTurns the setting on or off.

Wording

  1. Toggle labels are nouns naming the setting, in sentence case: "Dark mode", not "Enable dark mode".
  2. Never change the label when the state changes.

All content rules

Fits with

Researched from