SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/dark-mode-toggles

7 CSS Dark Mode Toggles

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.

01. Sun That Turns into a Moon

Dark modeIcon

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.

View code
<button class="dmt1" aria-pressed="false" aria-label="Dark mode"><svg viewBox="0 0 24 24" aria-hidden="true"><mask id="dmt1m"><rect width="24" height="24" fill="#fff"/><circle class="bite" cx="30" cy="4" r="8" fill="#000"/></mask><circle class="body" cx="12" cy="12" r="5" mask="url(#dmt1m)"/><g class="rays"><path d="M12 1.5v2.5M12 20v2.5M1.5 12h2.5M20 12h2.5M4.6 4.6l1.8 1.8M17.6 17.6l1.8 1.8M4.6 19.4l1.8-1.8M17.6 6.4l1.8-1.8"/></g></svg></button>
.dmt1 { display: grid; place-items: center; width: 56px; height: 56px; border: 1px solid #d0d5dd; border-radius: 50%; background: #fff; color: #b54708; cursor: pointer; transition: background .3s, color .3s; }
.dmt1 svg { width: 28px; height: 28px; }
.dmt1 .body { fill: currentColor; transition: r .3s; }
.dmt1 .bite { transition: cx .35s ease, cy .35s ease; }
.dmt1 .rays { stroke: currentColor; stroke-width: 2; stroke-linecap: round; transform-origin: center; transition: transform .35s ease, opacity .35s; }
.dmt1[aria-pressed=true] { background: #0f172a; color: #e2e8f0; border-color: #334155; }
.dmt1[aria-pressed=true] .body { r: 9px; } .dmt1[aria-pressed=true] .bite { cx: 17px; cy: 7px; }
.dmt1[aria-pressed=true] .rays { transform: scale(.5) rotate(45deg); opacity: 0; }
.dmt1:focus-visible { outline: 3px solid #84adff; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .dmt1, .dmt1 * { transition: none !important; } }
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.

View code
<button class="dmt2" role="switch" aria-checked="false" aria-label="Dark mode"><span class="s" aria-hidden="true">☀</span><span class="m" aria-hidden="true">☾</span><span class="k" aria-hidden="true"></span></button>
.dmt2 { position: relative; display: flex; align-items: center; justify-content: space-between; width: 72px; height: 36px; padding: 0 9px; border: 0; border-radius: 18px; background: #93c5fd; cursor: pointer; transition: background .3s; }
.dmt2 span { font-size: 15px; line-height: 1; } .dmt2 .s { color: #b45309; } .dmt2 .m { color: #e0e7ff; }
.dmt2 .k { position: absolute; top: 4px; left: 4px; width: 28px; height: 28px; border-radius: 50%; background: #fff; box-shadow: 0 2px 4px rgba(0,0,0,.3); transition: transform .3s cubic-bezier(.2,.8,.2,1); transform: translateX(36px); }
.dmt2[aria-checked=true] { background: #312e81; } .dmt2[aria-checked=true] .k { transform: none; }
.dmt2:focus-visible { outline: 3px solid #fde68a; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .dmt2, .dmt2 .k { transition: none; } }
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.

View code
<fieldset class="dmt3"><legend class="sr">Theme</legend><label title="System"><input type="radio" name="dmt3" checked><span class="sr">System</span><span aria-hidden="true">🖥</span></label><label title="Light"><input type="radio" name="dmt3"><span class="sr">Light</span><span aria-hidden="true">☀</span></label><label title="Dark"><input type="radio" name="dmt3"><span class="sr">Dark</span><span aria-hidden="true">☾</span></label></fieldset>
.dmt3 { display: inline-flex; gap: 2px; min-inline-size: 0; margin: 0; padding: 3px; border: 1px solid #e4e7ec; border-radius: 10px; background: #f2f4f7; }
.dmt3 label { display: grid; place-items: center; width: 40px; height: 34px; border-radius: 8px; font-size: 16px; cursor: pointer; }
.dmt3 input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.dmt3 label:has(:checked) { background: #fff; box-shadow: 0 1px 3px rgba(16,24,40,.2); }
.dmt3 label:has(:focus-visible) { outline: 3px solid #84adff; outline-offset: 1px; }
.dmt3 .sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

04. Day and Night Scene Toggle

Dark modeIllustrated

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.

View code
<button class="dmt4" role="switch" aria-checked="false" aria-label="Night mode"><span class="k" aria-hidden="true"></span></button>
.dmt4 { position: relative; width: 96px; height: 44px; padding: 0; border: 0; border-radius: 22px; overflow: hidden; background: linear-gradient(#7dd3fc, #bae6fd); box-shadow: inset 0 2px 4px rgba(0,0,0,.2); cursor: pointer; transition: background .5s; }
.dmt4::before { content: ""; position: absolute; inset: 0; background: radial-gradient(1.5px 1.5px at 20px 12px, #fff, transparent), radial-gradient(1px 1px at 40px 28px, #fff, transparent), radial-gradient(1.5px 1.5px at 30px 34px, #fff, transparent), radial-gradient(1px 1px at 52px 10px, #fff, transparent); opacity: 0; transition: opacity .5s; }
.dmt4 .k { position: absolute; top: 6px; left: 6px; width: 32px; height: 32px; border-radius: 50%; background: radial-gradient(circle at 40% 40%, #fef08a, #f59e0b); box-shadow: 0 0 12px #fbbf24; transition: transform .5s cubic-bezier(.2,.8,.2,1), background .5s, box-shadow .5s; }
.dmt4[aria-checked=true] { background: linear-gradient(#0f172a, #1e1b4b); } .dmt4[aria-checked=true]::before { opacity: 1; }
.dmt4[aria-checked=true] .k { transform: translateX(52px); background: radial-gradient(circle at 70% 30%, transparent 8px, #e2e8f0 9px); box-shadow: 0 0 10px rgba(226,232,240,.5); }
.dmt4:focus-visible { outline: 3px solid #fde68a; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .dmt4, .dmt4 *, .dmt4::before { transition: none !important; } }
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.

View code
<label class="dmt5"><input type="checkbox" role="switch"><span class="t" aria-hidden="true"></span>Dark mode</label>
.dmt5 { display: inline-flex; align-items: center; gap: 10px; color: #101828; font: 600 15px Inter, system-ui, sans-serif; cursor: pointer; }
.dmt5 input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.dmt5 .t { position: relative; width: 44px; height: 24px; border-radius: 12px; background: #d0d5dd; transition: background .2s; }
.dmt5 .t::after { content: ""; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%; background: #fff; transition: transform .2s; }
.dmt5 input:checked + .t { background: #4f46e5; } .dmt5 input:checked + .t::after { transform: translateX(20px); }
.dmt5 input:focus-visible + .t { outline: 3px solid #a5b4fc; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .dmt5 .t, .dmt5 .t::after { transition: none; } }

06. Eclipse Icon Toggle

Dark modeAnimation

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.

View code
<button class="dmt6" aria-pressed="false" aria-label="Dark theme"><span aria-hidden="true"></span></button>
.dmt6 { display: grid; place-items: center; width: 52px; height: 52px; border: 1px solid #334155; border-radius: 14px; background: #151b2e; cursor: pointer; }
.dmt6 span { width: 24px; height: 24px; border: 2px solid #f8fafc; border-radius: 50%; background: linear-gradient(90deg, #f8fafc 50%, transparent 50%); transition: transform .4s cubic-bezier(.2,.8,.2,1); }
.dmt6[aria-pressed=true] span { transform: rotate(180deg); }
.dmt6:focus-visible { outline: 2px solid #93c5fd; outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .dmt6 span { transition: none; } }
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>
.dmt7 { --bg: #ffffff; --fg: #101828; --muted: #475467; width: 280px; max-width: 100%; box-sizing: border-box; padding: 18px; border: 1px solid #e4e7ec; border-radius: 14px; background: var(--bg); color: var(--fg); font: 14px Inter, system-ui, sans-serif; transition: background .3s, color .3s; }
.dmt7:has(input:checked) { --bg: #0f172a; --fg: #f1f5f9; --muted: #94a3b8; border-color: #334155; }
.dmt7 label { display: flex; align-items: center; gap: 8px; font-weight: 600; cursor: pointer; }
.dmt7 input { width: 18px; height: 18px; margin: 0; accent-color: #6366f1; }
.dmt7 input:focus-visible { outline: 3px solid #a5b4fc; outline-offset: 2px; }
.dmt7 h3 { margin: 14px 0 4px; } .dmt7 p { margin: 0; color: var(--muted); }
@media (prefers-reduced-motion: reduce) { .dmt7 { transition: none; } }

About these CSS Dark Mode Toggles

Pick the right control

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.