Toggle

Accessible switch: a real <button role="switch"> with aria-checked in sync with state. No hidden checkbox needed; one boolean field controls the visual and the announced state.

Live preview

Markup

<div data-component="toggle-demo" data-label="Email notifications">
  <button class="toggle" role="switch" type="button"
    @click="flip"
    data-class="on:checked, disabled:disabled"
    data-bind="aria-checked:checked, aria-disabled:disabled">
    <span class="toggle-track"><span class="toggle-thumb"></span></span>
    <span class="toggle-label" data-text="label"></span>
  </button>
</div>

Definition

Micra.define('toggle-demo', {
  state: { checked: false, label: '', disabled: false },

  onCreate() {
    // this.prop() auto-casts "true"/"false" to booleans, so a default of
    // `false` covers both "attribute missing" and "data-checked='false'".
    this.state.label    = this.prop('label', '')
    this.state.checked  = this.prop('checked', false)
    this.state.disabled = this.prop('disabled', false)
  },

  flip() {
    if (this.state.disabled) return
    this.state.checked = !this.state.checked
    Micra.emit('toggle:changed', {
      label:   this.state.label,
      checked: this.state.checked,
    })
  },
})

Integration

  • Real semantics. role="switch" + aria-checked let screen readers announce "on" / "off". Wrapping in a <label> isn't needed because the button itself is the labeled target.
  • Props from the server. Initial value, label, and disabled flag come in via data-* attrs; read once in onCreate with this.prop().
  • Bus notification. Micra.emit('toggle:changed', …) lets any other component (e.g. a save-bar showing "Saving…") react without sharing a parent.
  • Keyboard. Native button gives you Space and Enter for free — no extra handler needed.

Pitfalls

  • Don't use a hidden checkbox + custom label — the focus order gets weird, the value isn't directly bindable, and aria-checked on the visible control is what assistive tech actually reads.
  • Don't put two state fields like on and off — derive one from the other if you must (off() { return !this.state.checked }).
  • Don't forget the type="button" attribute when the toggle lives inside a form — without it, pressing Space submits the form.