3 Pure CSS4 with JSPublished 2026-10-05Free · no sign-up
Seven theme toggles: an icon whose sun is eclipsed into a moon, a switch with a sun and moon on its track, three icon choices for system, light and dark, a little day and night landscape, a plain labelled switch, a half-filled eclipse icon and a toggle that themes a panel with no script.
An icon button whose sun shrinks its rays and is eclipsed into a crescent moon when dark mode is on. The crescent is a second circle masking the first, the rays are a separate group that scales away, and aria-pressed carries the state.
var b = document.querySelector('.dmt1');
b.onclick = function () { b.setAttribute('aria-pressed', b.getAttribute('aria-pressed') !== 'true'); };
02. Switch with Sun and Moon Track
Dark modeSwitch
A switch whose track shows a small sun and moon, with the knob sliding to cover whichever is not active. It is a button with role="switch" and aria-checked, named Dark mode, so it is announced as a switch that is on or off.
var b = document.querySelector('.dmt2');
b.onclick = function () { b.setAttribute('aria-checked', b.getAttribute('aria-checked') !== 'true'); };
03. System, Light and Dark Icons
Dark modeSegmented
A compact segmented control with three icon options, each a radio button with a visually hidden text label: System, Light and Dark. The selected option is raised with :has(:checked), and arrow keys move between options as in any radio group.
A playful switch drawn as a little landscape: a sun in a blue sky that slides across to become a moon in a starry sky. Everything is CSS gradients and one knob, and underneath it is a normal switch button with a clear name.
var b = document.querySelector('.dmt4');
b.onclick = function () { b.setAttribute('aria-checked', b.getAttribute('aria-checked') !== 'true'); };
05. Toggle with a Visible Label
Dark modeLabel
The plainest and often the clearest version: a switch with the words Dark mode beside it, where the text is the label for a real checkbox styled as a switch. Clicking the words works too, and the checkbox reports on and off natively.
A circle that is half filled and rotates 180 degrees when toggled, the half-moon symbol for contrast or theme. It is one element with a two-stop conic gradient, so the whole icon is a background and a rotation.
var b = document.querySelector('.dmt6');
b.onclick = function () { b.setAttribute('aria-pressed', b.getAttribute('aria-pressed') !== 'true'); };
07. Theme Switch Without JavaScript
Dark modeNo JS
A checkbox inside a page wrapper changes the whole wrapper's theme with :has(:checked), with no script at all. The choice is not remembered between visits without script, so combine it with the localStorage pattern for a real site.
View code
<div class="dmt7"><label><input type="checkbox"> Dark theme</label><h3>Weekly update</h3><p>This whole panel changes theme from one checkbox, using :has().</p></div>
A two-state toggle is a button with aria-pressed or a role="switch" with aria-checked. A choice between system, light and dark is a radio group. Use the one that matches the choice.
Name the setting, not the icon
Icon-only toggles need an accessible name such as Dark mode. The state (on, off, pressed) comes from the attribute, so do not change the name when the state changes.
Remember the choice
Store an explicit choice in localStorage and apply it before the page paints to avoid a flash of the wrong theme. Keep System as the default so the operating system decides.
Delight, briefly
Animated icons are fun, but keep the animation under half a second and turn it off under prefers-reduced-motion.
Frequently asked questions
Where should the theme toggle go?
In the header or settings, in the same place on every page.
How do I avoid a flash of the light theme?
Read the saved choice in a tiny inline script in the head and set the theme attribute before CSS renders.
See the theme system itself?
The dark mode variables collection shows the tokens these toggles switch.