Design Systems

Implementing Dark Mode with CSS: Variables, Media Queries, and Toggles

Dark mode has moved from novelty to expectation. Both iOS and Android support system-level dark mode preferences, and users expect websites to respect them. The implementation is straightforward if your palette is built on CSS custom properties (which PaletteRx exports by default).

Step 1: CSS Variables for Both Modes

:root {
  --bg: var(--color-light-base);
  --text: var(--color-dark-base);
  --surface: #ffffff;
  --accent: var(--color-primary);
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: var(--color-dark-base);
    --text: var(--color-light-base);
    --surface: #1e1e2e;
    --accent: #818cf8; /* lighter variant */
  }
}

Step 2: Manual Toggle

A manual toggle lets users override the system preference. Store the choice in localStorage and apply a data attribute to the html element:

[data-theme="dark"] {
  --bg: var(--color-dark-base);
  --text: var(--color-light-base);
  --surface: #1e1e2e;
  --accent: #818cf8;
}

The data attribute takes precedence over the media query when present.

Step 3: Smooth Transitions

Add transitions to background and color changes: transition: background-color 200ms ease, color 50ms ease; Background transitions feel smooth at 200ms. Text color should change nearly instantly (50ms) to avoid readability issues during transition.

Dark Mode Color Adjustments

Do not simply invert all colors. Your primary may need to be lighter in dark mode for adequate contrast against dark backgrounds. Borders become lighter (not darker). Shadows become subtler or disappear. Surface colors need multiple dark levels for depth hierarchy.

📘 PaletteRx foundation: Your PaletteRx light base and dark base are the anchor points for mode switching. Everything else derives from these two values. Build both modes' color stacks from these anchors.

Ready to Build Your Palette?

PaletteRx guides you from color selection to accessible, export-ready design systems in minutes.

🎨 Launch PaletteRx