Dark Mode Implementation
Concept of the skill
Dark mode implementation is the discipline of making a two-state light/dark switch work flawlessly in a real browser over a palette that has already been designed elsewhere. It is settled by five concerns, each with a well-defined web-platform primitive: detection of the user's preference (the prefers-color-scheme media query for the system default, plus a persisted explicit choice in localStorage or a cookie, exposed as the three states System / Light / Dark); first-paint application, where the resolved theme is set on <html> synchronously before stylesheets resolve — via a small blocking inline script in <head> or server-side serialization — so the user never sees a flash of the wrong theme; runtime propagation through a class or data attribute that paired custom properties (or the light-dark() function) respond to, alongside the CSS color-scheme property so native form controls, scrollbars, and the page background render correctly; asset variants for color-sensitive content — dark raster images via <picture> media, currentColor/custom-property SVGs, a dark favicon, themed iframes and videos; and browser-chrome hints via a per-scheme meta theme-color for the mobile address bar, Safari toolbar, and PWA splash. Throughout, the explicit user choice overrides the system preference, and "System" is a genuine third state rather than the absence of a saved choice. The skill's job is to make dark mode a correct runtime integration rather than a stylesheet swap.
Coverage
A dark mode implementation handles five concerns: detecting the user's preference (system, explicit choice, persisted choice), applying the right theme before first paint, propagating theme changes at runtime, swapping color-sensitive assets, and updating browser-chrome hints. Each has well-defined web platform primitives.
Detection uses the prefers-color-scheme media query (window.matchMedia('(prefers-color-scheme: dark)')), which reflects the operating system or browser-level preference. Most products offer three user choices — System, Light, Dark — where System defers to the media query and the other two override it. The chosen mode is persisted (localStorage or a cookie) and read on every load. The CSS color-scheme property tells the user agent which schemes a page supports, enabling correct rendering of native form controls, scrollbars, and the default page background; declare color-scheme: light dark on :root for sites that support both.
Flash-of-incorrect-theme (sometimes called FOUC for theme) occurs when the browser paints the light default before the persisted dark preference is applied. The fix is a small, blocking inline script in that reads the persisted preference and sets a class or data attribute on synchronously before stylesheets resolve. Frameworks with server-side rendering must serialize the resolved theme into the HTML response, often by reading a cookie on the server.
The CSS Color Module Level 5 light-dark() function (light-dark(white, black)) lets a property declare both schemes inline and the user agent picks based on color-scheme. This is supported in current browsers and reduces the boilerplate of paired custom properties for simple cases; for token-driven systems, paired :root and [data-theme="dark"] custom property assignments remain the typical approach.
Asset handling covers three categories. Raster images that have brand-color elements need dark variants delivered via with (the markup-driven approach) or via CSS background-image swapping. SVG illustrations can use currentColor or CSS custom properties for fill/stroke and update automatically. Favicons can declare a dark variant via . Embedded videos and iframes (YouTube, Maps) often have their own theme parameter that needs to be passed via URL.
Browser chrome hints include the meta theme-color tag (), which sets the address bar color on mobile browsers, the Safari toolbar tint, and the PWA splash screen. Pair it with a light variant via media= so the chrome matches the active theme. Apple-specific apple-mobile-web-app-status-bar-style is now overridden by theme-color on supported versions.
Philosophy of the skill
Dark mode is not a re-skin; it is a parallel design surface. Image contrast, shadow strategy (shadows on dark backgrounds work differently — often replaced with subtle borders or elevated background tints), focus ring visibility, and chart palettes all need attention. Treating dark mode as a CSS-only change produces a dark mode that technically works and feels half-finished.
The user's explicit choice overrides the system. A user who has toggled to Dark on your site once expects Dark on the next visit regardless of what their OS is doing. Persistence is not optional, and a System option is a separate, third state from "no preference saved."
Verification
- No flash of incorrect theme occurs on cold load for a dark-mode user; verified by throttling network and watching the first paint.
- The CSS color-scheme property is declared on :root with the schemes the page supports, so native form controls render correctly in dark mode.
- Brand-color images (illustrations, hero photos with light backgrounds) have dark variants delivered via source matching or equivalent.
- The meta theme-color tag has paired light and dark variants and matches the active theme.
- The favicon has a dark variant where the design needs it (e.g., dark logos on transparent backgrounds become invisible against dark browser tabs).
- The user's explicit choice (light/dark/system) is persisted and restored across reloads; toggling away from System and back is observable.
- All interactive states (focus rings, hover, selected) are visible in dark mode and meet contrast requirements; verified visually and via automated contrast testing.
Do NOT Use When
- The task is choosing the dark palette values themselves. Use color-system-design.
- The work is defining the token architecture that makes theming possible. Use theme-system-design.
- The product requires more than two themes (multi-brand white-label, three or more visual modes). Use theme-system-design for the architecture; this skill covers the dark-specific platform integrations only.
- You are auditing for WCAG contrast violations specifically. Use a11y for the criteria; the palette decisions come from color-system-design.
- The bug is a single component looking wrong in dark mode after the system is otherwise complete. That is component-level debugging, not implementation architecture.