Skip to content

Switch

hc-switch is applied to a standard <input type="checkbox" role="switch">. The underlying input keeps every native behaviour — Space toggles, the value participates in form submission, screen readers announce it as a switch via the role override — and only the visual chrome is replaced via appearance: none.

Also known as: toggle, toggle switch.

.hc-switch-label is an inline-flex helper that clusters the switch and its text so a click on the label toggles the input.

data-variant accepts success, warning, and error for non-default checked-state tints — the same trio as checkbox and radio. The default (omitted) variant uses the active color theme’s primary fill.

data-size accepts sm, md (default), and lg.

The switch participates in form submission like any native checkbox. For instant toggles, listen to change:

<label class="hc-switch-label">
<input
class="hc-switch"
type="checkbox"
role="switch"
name="notifications"
data-hx-post="/preferences/notifications"
data-hx-trigger="change">
Email notifications
</label>

When the switch is part of a plain form post and the server binds a declared boolean, remember that an unchecked switch submits nothing — use the blessed boolean-field pattern (hidden false + switch true under one name); it applies to hc-switch verbatim.

A feature flag, a per-user preference: the switch is the save, there is no Save button. Rather than giving each switch its own request, keep the form as the unit — it is what the mutating-form recipe already owns (target, swap, 422 re-render) — and let the switch post it the moment it changes with data-hc-submit-on-change:

<form method="post" action="/flags"
data-hx-post="/flags" data-hx-target="closest .hc-card"
data-hx-swap="outerHTML" data-hx-sync="this:replace">
<label class="hc-switch-label">
<input type="hidden" name="beta" value="false">
<input class="hc-switch" type="checkbox" role="switch"
name="beta" value="true" data-hc-submit-on-change>
Beta features
</label>
<noscript><button class="hc-button" type="submit">Save</button></noscript>
</form>

On change the behavior calls form.requestSubmit(): constraint validation runs, the form’s submit event fires, and htmx takes the request from there — so hx-sync, the dirty guard, the CSRF header and data-hc-close-dialog-on-success all apply, exactly as if a Save button had been pressed. The attribute may also sit on a <fieldset>: any descendant’s change bubbles to it, so a settings group submits as one form.

Two things the behavior decides for you, once: a control that carries its own htmx verb (the data-hx-post shape above) is left alone, and so is a form whose hx-trigger already includes change — either way htmx is driving, and a second submit would double-fire. Without JavaScript the switch simply does not auto-submit; the <noscript> Save button keeps the no-JS path honest.

  • Always pair the input with a label. Either wrap both in <label class="hc-switch-label"> or use a separate <label for> associated by id.
  • role="switch" is the semantic role for a binary on/off toggle — prefer it over a plain checkbox so screen readers announce “switch, on/off” rather than “checkbox, checked/unchecked”. The underlying element stays an <input type="checkbox"> for form serialisation; the ARIA role override only changes how it’s announced.
  • Don’t remove the focus outline. The component replaces the default outline with a visible box-shadow ring driven by --hc-color-focus-ring.
  • The thumb-slide transition respects prefers-reduced-motion: reduce — motion is suppressed when the user has opted out.

Component tokens (in component.tokens.json):

Token path Purpose
switch.width / height Track dimensions.
switch.thumb-size Diameter of the moving thumb.
switch.padding Gap between thumb and track edge.
switch.bg / thumb-bg Off-state colors.
switch.checked-bg / checked-border Default-variant on-state.
switch.success-checked-bg / warning-checked-bg / error-checked-bg Variant on-state.
switch.disabled-bg Background when disabled.
switch.label-gap Gap between switch and its text.
switch.sm.* / lg.* Dedicated sm / lg overrides.
Show the generated CSS variables
--hc-switch-width | -height | -thumb-size | -padding
--hc-switch-border-width | -border
--hc-switch-bg | -thumb-bg
--hc-switch-checked-bg | -checked-border
--hc-switch-success-checked-bg | -warning-checked-bg | -error-checked-bg
--hc-switch-disabled-bg | -label-gap
--hc-switch-sm-width | -sm-height | -sm-thumb-size
--hc-switch-lg-width | -lg-height | -lg-thumb-size
--hc-color-focus-ring (inherited from data-color)
  • Checkbox — when the metaphor is “agree / opt-in” rather than “turn this on/off”.
  • Radio — single pick from a small fixed set.