P
Pixel Show
CSSUI/UX

Implementing Dark Mode the Right Way

A complete guide to dark mode implementation — from color tokens to system preference detection to persistent user choice.

Dark Mode Is Not Just Inverting Colors

The biggest mistake in dark mode implementations is thinking you can just swap white backgrounds to black and call it done. Good dark mode requires a separate color palette designed for dark surfaces.

Color Strategy

Dark mode colors need to account for:

  • Reduced contrast — pure white (#fff) on pure black (#000) is harsh. Use off-white (#e5e5e5) on dark gray (#0a0a0a)
  • Elevated surfaces — use slightly lighter backgrounds for cards and modals to create depth
  • Accent colors — some vibrant colors need to be desaturated or lightened to remain accessible on dark backgrounds
  • Shadows — dark mode shadows barely work on dark backgrounds; use subtle borders or lighter elevated surfaces instead

CSS Custom Properties Approach

Define your colors as CSS variables and override them per theme:

:root {
  --bg: #ffffff;
  --text: #1a1a1a;
  --border: #e5e5e5;
}

[data-theme="dark"] { --bg: #0a0a0a; --text: #e5e5e5; --border: #1e1e1e; }

Every component references these variables. Switching themes updates everything instantly.

Detecting System Preference

CSS can detect the user's OS theme preference:

@media (prefers-color-scheme: dark) {
  :root { / dark values / }
}

Persistent User Choice

Use JavaScript to store the user's explicit choice in localStorage. The priority order should be: explicit user choice, then system preference, then your default.

Avoiding the Flash

The flash of wrong theme on page load happens because JavaScript runs after HTML renders. Solutions:

  • Use a blocking