Dark Mode CSS: The Complete Implementation Guide

Dark mode is no longer optional. Learn three CSS strategies — system preference detection, class toggle, and cookie persistence — with smooth transitions and zero flash.

U

UIXplor Team

February 25, 2026 · 7 min read

01prefers-color-scheme: The System-First Approach

The `prefers-color-scheme` media query detects the OS setting automatically:

css
:root { --bg: #ffffff; --text: #111111; }
@media (prefers-color-scheme: dark) {
  :root { --bg: #0a0a0f; --text: rgba(255,255,255,0.85); }
}
body { background: var(--bg); color: var(--text); }

No JavaScript needed. Updates instantly when the user changes their OS preference.

02Class-Based Toggle

For user-controlled toggles, use a class on `<html>`:

css
html.dark { --bg: #0a0a0f; --text: rgba(255,255,255,0.85); }
html.light { --bg: #ffffff; --text: #111111; }
body { background: var(--bg); color: var(--text); transition: background 0.3s ease, color 0.3s ease; }
js
const toggle = () => document.documentElement.classList.toggle('dark');

03Preventing Flash of Wrong Theme

Add this blocking script in `<head>` (before any CSS) to apply the saved theme before paint:

html
<script>
  const saved = localStorage.getItem('theme');
  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
  document.documentElement.classList.add(saved || (prefersDark ? 'dark' : 'light'));
</script>

This eliminates the flash by applying the class synchronously during HTML parsing.

04Dark Mode Image Handling

Reduce bright image intensity in dark mode:

css
@media (prefers-color-scheme: dark) {
  img:not([src*='.svg']) { filter: brightness(0.85) contrast(1.05); }
}

/* Or use the picture element for different sources */

Also invert SVG icons that are dark-on-white:

css
.icon-auto { filter: invert(0); }
@media (prefers-color-scheme: dark) { .icon-auto { filter: invert(1); } }