Dark Mode with CSS Variables and light-dark()

A
Author
26 Apr, 2026 1 min read 237 views

Good dark mode isn't just inverted colours. It needs its own palette, respects the user's system setting, and lets them override it.

1. Use semantic colour variables

:root {
  --bg: #ffffff;
  --surface: #f8fafc;
  --text: #0f172a;
  --muted: #64748b;
  --brand: #0d9488;
}

body { background: var(--bg); color: var(--text); }

Components use --surface and --text, never raw colours, so switching themes only means changing the variables.

2. Follow the system setting

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0b1120;
    --surface: #111827;
    --text: #e2e8f0;
    --muted: #94a3b8;
    --brand: #2dd4bf;
  }
}

3. Or use light-dark()

:root {
  color-scheme: light dark;   /* also styles scrollbars and form controls */
  --bg: light-dark(#ffffff, #0b1120);
  --text: light-dark(#0f172a, #e2e8f0);
}

light-dark() picks the first value in light mode and the second in dark mode, based on the element's color-scheme.

4. Add a toggle that remembers

const saved = localStorage.getItem('theme'); // 'light' | 'dark' | null
if (saved) document.documentElement.style.colorScheme = saved;

toggle.addEventListener('click', () => {
  const systemDark = matchMedia('(prefers-color-scheme: dark)').matches;
  const current = document.documentElement.style.colorScheme || (systemDark ? 'dark' : 'light');
  const next = current === 'dark' ? 'light' : 'dark';
  document.documentElement.style.colorScheme = next;
  localStorage.setItem('theme', next);
});

Setting color-scheme on the root element switches every light-dark() value at once. Run the "read saved theme" part in a small inline script in the <head> so the page doesn't flash the wrong theme on load.

Dark palette tips

  • Avoid pure black backgrounds; very dark blue-grey is easier on the eyes.
  • Lower saturation of bright brand colours slightly.
  • Show elevation with lighter surfaces instead of shadows.
  • Re-check text contrast; it's easy to go too dim.
Was this article helpful?

Comments

No comments yet. Have a question or a tip? Be the first to share it.

Leave a comment

Comments are reviewed before they appear.