10 CSS Dark Mode Toggles

10 production-grade CSS dark mode toggle designs you can copy-paste into any project — pure CSS checkbox hack with :has() (zero JavaScript), animated sun-to-moon micro-interaction with morphing icon, modern light-dark() CSS function boilerplate (Chrome 123+ / Safari 17.5+), 3-way system-sync toggle (light / dark / system — the shadcn / Vercel / Anthropic pattern), localStorage anti-flash template that eliminates FOUC on page load, Tailwind data-theme dashboard toggle with selector-strategy config, neumorphic skeuomorphic tactile switch with soft inset/outset shadows, full-page expanding circular clip-path transition (the iOS-style sweep, View Transitions API + CSS fallback), accessible ARIA toggle with aria-pressed + :focus-visible + live region announcements, and sidebar-bottom fixed toggle for dashboard layouts. Every toggle scoped under a unique .dm-NN prefix so all 10 coexist on this page without style bleed; every @keyframes name namespaced dm-NN-*; every @media (prefers-reduced-motion: reduce) guard already wired in. Each demo ships the full localStorage + prefers-color-scheme + data-theme recipe so you can paste it into any framework — Astro, Next.js, SvelteKit, Eleventy, vanilla — and it just works.

1 pure CSS9 light JSPublished

Related5 CSS Variable Dark Mode Systems22 CSS Dark Mode UI47 CSS Toggles & Switches

Pure CSS Checkbox Hack Toggle — preview
01 / 10Pure CSS

Pure CSS Checkbox Hack Toggle

A zero-JavaScript theme switcher driven by a hidden input:checked state propagated upward through the DOM via CSS :has() — no event listeners, no hydration cost.

Published

Animated Sun-to-Moon Micro-Interaction — preview
02 / 10CSS + JS

Animated Sun-to-Moon Micro-Interaction

A sky panorama where a sun morphs into a crescent moon — rays collapse via CSS transforms, a moon rises with spring easing, craters fade in, and stars twinkle into view. Zero images, zero SVG files.

Published

The Modern CSS light-dark() Function Boilerplate — preview
03 / 10CSS + JS

The Modern CSS light-dark() Function Boilerplate

A mock code editor showcasing the native CSS light-dark() function — single declarations that adapt both values simultaneously, eliminating duplicate @media blocks entirely.

Published

3-Way System Sync Toggle — preview
04 / 10CSS + JS

3-Way System Sync Toggle

A glassmorphism segmented control with Light, Dark, and System modes — the glider pill positions itself using live getBoundingClientRect(), and System mode wires a matchMedia listener to the OS.

Published

LocalStorage Anti-Flash Template — preview
05 / 10CSS + JS

LocalStorage Anti-Flash Template

An interactive simulation of the anti-FOUC pattern: an inline blocking IIFE reads localStorage before CSS loads and applies a data-dm05 attribute to prevent any flash of white on dark pages.

Published

Tailwind CSS data-theme Dashboard Toggle — preview
06 / 10CSS + JS

Tailwind CSS data-theme Dashboard Toggle

A full SaaS admin dashboard — sidebar, KPI cards, bar chart — where toggling sets data-theme=dark on the root element, flipping every component via attribute-scoped CSS custom properties.

Published

Neumorphic Skeuomorphic Tactile Toggle Switch — preview
07 / 10CSS + JS

Neumorphic Skeuomorphic Tactile Toggle Switch

A physically satisfying 3D-molded toggle — extruded in light mode with outset shadows, it literally sinks into the page with inset shadows on press. Dark mode adds a cyan neon glow radiating from the thumb.

Published

Full-Page Expanding Circular Clip-Path Transition — preview
08 / 10CSS + JS

Full-Page Expanding Circular Clip-Path Transition

A cinematic radial wipe where the dark theme expands as a growing circle from the exact pixel position of the button click — achieved with clip-path: circle() and CSS custom properties for the origin point.

Published

Accessible ARIA Toggle with Native Focus & ARIA States — preview
09 / 10CSS + JS

Accessible ARIA Toggle with Native Focus & ARIA States

A fully accessible dark mode switch with role=switch, aria-checked, keyboard navigation, and a live ARIA debugger panel that shows every attribute update in real time.

Published

Sidebar Bottom Fixed Toggle — preview
10 / 10CSS + JS

Sidebar Bottom Fixed Toggle

A collapsible vertical navigation bar with the theme switch anchored at the bottom — the sidebar collapses from 240px to 70px with labels fading out while the toggle remains reachable as an icon-only button.

Published

FAQ

Frequently asked questions

How do I make a dark mode toggle in pure CSS without JavaScript?
The pattern is a hidden checkbox input paired with the modern CSS :has() selector. Place an input type=checkbox anywhere inside your wrapper (visually hidden with clip-path: inset(50%) or opacity: 0; position: absolute), wire it to a visible label via for=id, and write a single CSS rule: .wrapper:has(input:checked) { --bg: #0f172a; --text: #f0f4ff; ... }. The :has() selector evaluates from the parent looking *down* into its descendants, so checking the hidden input redefines every CSS custom property on the wrapper. The whole tree repaints in dark palette with zero JS, zero hydration cost, zero re-render. Browser support: Chrome 105+, Safari 15.4+, Firefox 121+ — combined ~95% of users as of 2026. For older browsers, fall back to the sibling-combinator pattern (input:checked ~ .wrapper). Demo 01 ships the full recipe with sun/moon icon swap on the pill knob.
How do I prevent the dark mode flash (FOUC) when a page loads?
The flash happens because: page paints in default theme → JS runs → reads localStorage → applies dark class → page repaints in dark. To prevent it, you must apply the theme *before* the first paint. The fix is an inline blocking script at the very top of the document head (before any stylesheet or other JS) that calls document.documentElement.setAttribute('data-theme', localStorage.getItem('theme') || (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')). This runs synchronously, blocks rendering for ~1-2ms (negligible), reads the stored theme + falls back to OS preference, sets the attribute on html before any CSS evaluates. Your CSS then targets [data-theme='dark'] for dark styles. No flash, ever. Demo 05 ships this template — the most copy-pasted dark-mode snippet on the web.
What's the new CSS light-dark() function and how do I use it?
light-dark() (CSS Color Module Level 5) is a function that takes two color arguments and returns whichever matches the current color-scheme. Usage: color: light-dark(black, white); background: light-dark(#fff, #0f172a);. To activate it, declare color-scheme: light dark on :root and let prefers-color-scheme drive the choice — OR explicitly set color-scheme: dark (e.g. via a toggle setting color-scheme: dark on the root element) to override. Browser support: Chrome 123+, Safari 17.5+, Firefox 120+ — landed in 2024, broadly safe in 2026. The killer feature is no media queries, no custom properties juggling — just light-dark() everywhere a color goes. Demo 03 ships the boilerplate including the JS that switches color-scheme on toggle for instant theme flip without rebuilding the cascade.
How do I make a 3-state dark mode toggle (light / dark / system)?
Three radio inputs in a fieldset, each representing a state: light, dark, system. JS writes the chosen value to localStorage.setItem('theme', value) and applies the resolved theme to the root element: if value is 'light' or 'dark', set data-theme directly; if value is 'system', set data-theme to whatever matchMedia('(prefers-color-scheme: dark)').matches returns *and* listen on that media query so OS changes propagate live. On page load, the inline anti-flash script (see FAQ 2) reads the stored value, resolves it the same way, and applies before paint. This is the pattern shadcn UI, Vercel, Anthropic, GitHub, and Tailwind UI all use. Demo 04 ships the full 3-way radio + live OS-listener + persistence recipe in ~40 lines of JS.
How do I use Tailwind's `darkMode: 'class'` or `[data-theme]` strategy with a toggle?
Tailwind 4.0 ships a new selector strategy that lets you choose any selector for dark mode. Three configurations: (1) darkMode: 'class' — toggle by adding class='dark' to the root element; CSS targets .dark .your-class. (2) darkMode: ['selector', '[data-theme="dark"]'] — the modern recommended setup; CSS targets [data-theme='dark'] .your-class; JS just sets document.documentElement.dataset.theme = 'dark'. (3) darkMode: 'media' — automatic OS-preference only, no toggle possible. Use (2) when you need a user-controlled toggle. The Tailwind utilities dark:bg-slate-900 dark:text-white then work automatically. Demo 06 ships a complete dashboard with the data-theme strategy, including the toggle button + localStorage persistence + Tailwind class composition pattern.
How do I make my dark mode toggle accessible to screen readers?
Five rules: (1) Use a real button type='button' element, not a div with role='button'. The native button handles Enter/Space activation, focus, and screen-reader announcements for free. (2) Add aria-pressed='true' when dark mode is on, aria-pressed='false' when off (NOT aria-checked — that's for checkboxes/radios). The screen reader announces 'Dark mode toggle, pressed' / 'not pressed'. (3) Provide a visible text label like 'Theme' near the toggle, OR use aria-label='Toggle dark mode' on the button itself. Icons alone (sun/moon SVG) need accessible names. (4) Add a visible :focus-visible outline so keyboard users see the focus location. (5) When the user activates the toggle, announce the state change to assistive tech via an aria-live='polite' region — a div with role='status' and aria-live='polite' containing the announcement text. Demo 09 ships all five rules with a :focus-visible ring, aria-pressed toggle, and live region — Lighthouse accessibility score 100.
How do I animate the dark mode transition smoothly without jank?
Three approaches, increasing in fanciness. (1) **Simple cross-fade**: add transition: background-color 0.3s, color 0.3s, border-color 0.3s to elements that need to transition. Warning: do NOT add transition: all — it animates layout-affecting properties like width on hover, causing layout thrash. Limit the transition to color properties only. (2) **Disable transitions on first paint**: add a class like theme-transition-pending to the root element initially, then strip it after the page is interactive. Prevents transitions firing on load. (3) **Full-page expanding clip-path** (the iOS-style sweep): use the [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) — document.startViewTransition(() => applyTheme(newTheme)). Chrome 111+, Safari 18+. For older browsers, demo 08 ships a CSS-only fallback using clip-path: circle(0 at click-x click-y) animated to circle(150vmax) — gives the same expanding-circle reveal effect cross-browser.
How is this different from theme-toggle (npm), use-dark-mode (npm), or react-toggle-dark-mode?
Three differentiators. **(1) Zero dependencies, zero KB**: these collection demos are vanilla HTML/CSS/JS that work in any framework or no framework. theme-toggle is 6KB gzipped, use-dark-mode is 4KB + React-only, react-toggle-dark-mode is 12KB + React-only. For a site with a 80KB JS budget already, adding 4-12KB just for a toggle is wasteful when 40 lines of vanilla code does the same thing. **(2) Patterns, not packages**: this collection teaches you the actual CSS techniques (:has(), light-dark(), data-theme, prefers-color-scheme, View Transitions API) so you can build any variant you need, not just use the maintainer's API. The libraries hide those details behind an opaque hook. **(3) SSR/SSG safe by default**: every demo includes the inline anti-flash script and works with Astro, Next, SvelteKit, Eleventy, etc. The npm packages need wrapper code to be SSR-safe (and use-dark-mode was famously broken in Next.js for two years). Free, copy-paste, MIT-licensed, no signup, works in any project.

Related collections

30 CSS Badges preview

30 CSS Badges

30 hand-coded CSS badges for status indicators, notifications, membership tiers, live-data displays, and SEO / DevOps / financial dashboards — upload progress, typing indicator, transit line status, Core Web Vitals, ECG heartbeat, CI/CD build pipeline, countdown ring, live price ticker, keycap shortcut, wax seal, conference lanyard, and holographic collectibles. Copy-paste HTML and CSS, WCAG 2.2 accessible, MIT licensed.

20 CSS Banners & Alerts preview

20 CSS Banners & Alerts

20 hand-coded CSS banner and alert bars for e-commerce storefronts, SaaS dashboards, marketing sites, and compliance-bound products. Covers GDPR cookie consent with a real preferences panel, promo bars with countdown timers, free-shipping threshold progress, app-install smart banners, maintenance and incident status bars, newsletter signup bars, age verification gates, limited-stock urgency banners, live event announcements, browser upgrade notices, geo and currency switchers, sticky top announcement bars, bottom cookie bars, inline form validation alerts, icon-aligned alert banners, left-border accent alerts, diagonal stripe promos, full-width hero banners, animated gradient border alerts, and a text-wrapping laboratory that shows five strategies for the unbreakable string that breaks more banners than anything else. Every bar is scoped under a .ba-NN prefix for no-collision pasting, guards prefers-reduced-motion, carries the correct aria-live and role semantics, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.

25 CSS Blockquotes preview

25 CSS Blockquotes

25 hand-coded CSS blockquote designs for SaaS testimonials, editorial pull quotes, documentation callouts, and developer engineering blogs: large decorative quotation marks with ::before / ::after glyphs, Tailwind CSS blockquote component, pure CSS callout / admonition boxes for Docusaurus / Mintlify / Nextra documentation, responsive center-aligned quote banners, modern minimalist left-border editorial style, animated CSS gradient border, classic left border, brutalist high-contrast, glassmorphism frosted card, glowing neon border, article pull quote with text-wrap, speech bubble chat-style, thick vertical accent rail, customer testimonial cards with avatar and star rating, inline highlighted text, marker highlight underline, floating drop-shadow, aurora gradient background, Twitter / X card style, multi-column newspaper editorial, IDE code-comment style, Markdown-compatible defaults, JavaScript testimonial quote slider, expandable read-more, and one-click copy button. 22 Pure CSS + 3 Light JS (slider, expand, copy). Every design scoped under .bq-NN prefix for no-collision pasting, prefers-reduced-motion guarded per WCAG 2.3.3, WCAG 2.2 AA accessible, framework-agnostic (React, Vue, Svelte, Astro, Next.js, Nuxt, Remix, SvelteKit).

Search CodeFronts

Loading…