Ch. 5 · HTML & CSS

CSS Dark Mode with prefers-color-scheme

Support light and dark themes with prefers-color-scheme and custom properties, and avoid the flash of the wrong theme.

~2 min readintermediateupdated Oct 5, 2026

Dark mode follows the user’s system preference through the prefers-color-scheme media query. The clean implementation defines theme values as custom properties and swaps them in the query, and it sets color-scheme so the browser’s own UI matches.

Before you start

You should be comfortable with custom properties and media queries. This article covers system-driven and manual theming.

Step-by-step walkthrough

Step 1: Drive the theme from the preference

@media (prefers-color-scheme: dark) applies dark values when the user’s system prefers dark. Defining colors as custom properties on :root and overriding them in the query keeps every component using the same tokens without per-component branches.

Step 2: Set color-scheme

color-scheme: light dark tells the browser to render built-in UI such as form controls and scrollbars in a matching scheme, so native elements do not look out of place. The <meta name="color-scheme"> tag does the same before CSS loads.

Step 3: Avoid the flash and allow a manual override

A theme applied by JavaScript after load can flash the wrong colors. Set the initial scheme in the HTML or an inline script before content paints. For a manual toggle, store the choice and apply it as an attribute so it overrides the media query.

Worked scenario

The tokens swap and the browser UI follows.

:root {
  color-scheme: light dark;
  --bg: #ffffff;
  --fg: #111111;
}
@media (prefers-color-scheme: dark) {
  :root { --bg: #111111; --fg: #f5f5f5; }
}
body { background: var(--bg); color: var(--fg); }
CSS

Walk through the example

The tokens hold light values by default and are overridden when the system prefers dark, and the components consume the tokens. color-scheme makes native controls match. Setting the tokens on :root means one override re-themes the whole page.

Common mistake

Hardcoding colors in components so dark mode only changes some elements, or applying the theme with JavaScript after the first paint so users see a light flash before dark. Another is forgetting color-scheme, leaving white form controls in a dark page.

Verify the behavior

Toggle the system preference and confirm the page and native controls both switch. Reload and confirm there is no flash of the wrong theme. Test the manual toggle overriding the system setting.

Interview exercise

Why does color-scheme matter for dark mode?

Answer and reasoning

Without it, the browser renders native UI such as scrollbars, form controls and the default canvas in the light scheme, which clashes with a dark page. color-scheme: light dark (or the meta tag) tells the browser to use the matching scheme for those built-in elements, so the whole surface is consistent.

Continue learning

Compare token usage in Custom properties and theming in Reduced motion. Read the MDN prefers-color-scheme documentation and try the HTML and CSS interview questions.

More in HTML & CSS

read ✓HTML & CSS · easy

CSS aspect-ratio and Layout Stability

Reserve space for media with aspect-ratio, combine it with object-fit, and avoid the layout shift the old padding hack caused.

~2 min readread →
read ✓HTML & CSS · hard

The CSS :has() Selector

Style a parent based on its children with :has(), handle form states, and use it without hurting performance.

~2 min readread →
read ✓HTML & CSS · hard

CSS Cascade Layers

Control the cascade order with @layer, keep specificity low, and understand how unlayered styles fit in.

~2 min readread →
esc