Snippets /
Dark mode toggle
A no-JavaScript dark/light mode switch using :has() to toggle custom properties from a checkbox.
Sample card
Toggle the switch above to see the color scheme change in real time, no JS required.
Quick implementation
/* Default: dark mode */
:root {
color-scheme: dark;
--bg: oklch(0.16 0.01 260);
--fg: oklch(0.92 0.01 260);
--surface: oklch(0.22 0.015 260);
}
/* When the toggle checkbox is checked, switch to light */
:root:has(#theme-toggle:checked) {
color-scheme: light;
--bg: oklch(0.97 0.005 260);
--fg: oklch(0.2 0.02 260);
--surface: oklch(1 0 0);
}
body {
background: var(--bg);
color: var(--fg);
transition: background 0.3s ease, color 0.3s ease;
}
/* Toggle track */
.toggle-track {
position: relative;
width: 3rem;
height: 1.6rem;
background: oklch(0.35 0.03 260);
border-radius: 1rem;
transition: background 0.2s ease;
}
.toggle-track::after {
content: "";
position: absolute;
top: 0.2rem;
left: 0.2rem;
width: 1.2rem;
height: 1.2rem;
background: oklch(0.95 0 0);
border-radius: 50%;
transition: translate 0.2s ease;
}
#theme-toggle:checked ~ label .toggle-track {
background: oklch(0.6 0.18 270);
}
#theme-toggle:checked ~ label .toggle-track::after {
translate: 1.4rem 0;
}
Prompt this to your LLM
Paste this into ChatGPT, Claude, or any code-generating model to scaffold the pattern instantly.
Build a pure CSS dark mode toggle. Use a hidden checkbox
with id="theme-toggle" and a styled label acting as the
switch. On :root, define custom properties for --bg, --fg,
and --surface in dark mode (oklch values with low
lightness). Use :root:has(#theme-toggle:checked) to
override those properties with light-mode oklch values.
Set color-scheme: dark by default and color-scheme: light
when checked. Style a pill-shaped toggle track with a
sliding circle using ::after and the translate property.
Add smooth transitions on background and color.
Why this matters
Dark mode is no longer optional for modern sites. The :has() selector, now supported in all major browsers, lets you toggle an entire design system from a single checkbox without any JavaScript. This means the toggle works even if scripts fail to load, providing a resilient user experience. Combined with color-scheme, native form elements and scrollbars adapt automatically.
The logic
Custom properties for background, foreground, and surface colors are declared on :root with dark-mode values. The selector :root:has(#theme-toggle:checked) targets the document root when the checkbox is checked, overriding those properties with light-mode values. Because custom properties cascade, every element using var(--bg) or var(--fg) updates instantly. The color-scheme property tells the browser which palette to use for native UI like scrollbars, inputs, and system dialogs. A transition on background and color smooths the visual switch.
Accessibility & performance
The hidden checkbox should remain in the DOM with opacity: 0 rather than display: none so keyboard users can still tab to it. The <label> must use for to associate with the checkbox for screen readers. Add aria-label="Toggle dark mode" if the visible label text is ambiguous. The color transitions should respect prefers-reduced-motion. For persistence across page loads, a small script reading localStorage is still recommended, but the toggle itself requires no runtime JavaScript.