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); }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.