dark-mode-implementation
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.