Dark Mode Theme System with CSS Variables

Build a complete light/dark theme toggle using CSS custom properties with @media prefers-color-scheme and class-based switching.

Theme Systems

Detailed Explanation

Building a Dark Mode Theme System

Modern web apps need both light and dark themes. CSS custom properties make this straightforward: define your light palette in :root, override it in a dark context, and your entire UI updates without touching component code.

Two Approaches

1. Media Query (OS preference)

:root {
  --bg: #ffffff;
  --text: #18181b;
  --surface: #f4f4f5;
  --border: #e4e4e7;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0a0a0b;
    --text: #fafafa;
    --surface: #141415;
    --border: #27272a;
  }
}

2. Class Toggle (manual switch)

:root {
  --bg: #ffffff;
  --text: #18181b;
}
.dark {
  --bg: #0a0a0b;
  --text: #fafafa;
}

Choosing the Right Method

The media-query approach respects the user's OS setting automatically with zero JavaScript. The class-toggle approach requires a small script but gives users explicit control and avoids flash-of-wrong-theme (FOIT) issues when combined with a blocking script or cookie.

Combining Both

Many frameworks (like next-themes) combine both: read the OS preference as the default, let the user override it, and persist the choice. The CSS structure is the same — just switch which wrapper triggers the dark override.

Avoiding Flash of Unstyled Theme

Place a tiny blocking <script> in <head> that reads the saved preference from localStorage and adds the .dark class before the browser paints. This prevents the white flash that occurs when the dark override loads asynchronously.

Testing

Always test both themes against WCAG contrast requirements. Colors that meet AA in light mode may fail in dark mode if you simply invert them without recalculating contrast ratios.

Use Case

Web applications that need a robust light/dark theme system, supporting both OS-level preference detection and user-controlled toggles, without component-level style changes.

Try It — CSS Variable Generator

Open full tool