How to make a toggle switch
Short answer
Take a regular checkbox, remove its default look with appearance: none and draw a track and a knob:
<style>
.switch { appearance: none; position: relative; width: 44px; height: 24px; margin: 0; border-radius: 999px; background: silver; cursor: pointer; transition: background 0.2s; vertical-align: middle; }
.switch::before { content: ""; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%; background: white; transition: translate 0.2s; }
.switch:checked { background: seagreen; }
.switch:checked::before { translate: 20px 0; }
.switch:focus-visible { outline: 2px solid orange; outline-offset: 2px; }
</style>
<label><input class="switch" type="checkbox" role="switch" checked> Dark mode</label>It is still a checkbox: it toggles with a click on the label and with Space, and is submitted with the form. role="switch" tells a screen reader that it is a switch, so it says "on"/"off" instead of "checked".
A disabled switch
:disabled sets the styles for the disabled state:
.switch:disabled { opacity: 0.5; cursor: not-allowed; }A switch or a checkbox
A switch acts immediately, like a light switch: turn it on — the dark theme appears. If the choice applies only after pressing "Save", a regular checkbox is more familiar.
Common mistake
The switch is built from a <div> and a <span> with a click handler. It does not get focus, cannot be toggled from the keyboard and does not report its state to screen readers. The base must always be an <input type="checkbox"> or a <button>.
Functions used in this recipe
- <input type="checkbox"> — A checkbox is an independent yes/no choice; a radio button picks one option from a group.
- appearance — Turns off the built-in look of form controls so they can be styled from scratch.
- :checked — Matches a checked checkbox or radio button, and a selected `<option>`.
- ::before — Creates a virtual first child of the element — for decorative text or shapes.
- transition — Animates the change of a property from the old value to the new one.