# Dark Mode Implementation

> Use when implementing dark mode — prefers-color-scheme detection, theme persistence, flash-of-unstyled-theme prevention, color token mirroring, image and asset variants, and meta theme-color updates. Do NOT use for designing the dark palette itself, designing the token architecture, or generic theme-switching across more than two themes.

- Skill: `jacob-balslev/dark-mode-implementation-2` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add jacob-balslev/dark-mode-implementation-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jacob-balslev/dark-mode-implementation-2/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: CC-BY-4.0
- Author: jacob-balslev (https://skillmd.com/u/jacob-balslev)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/jacob-balslev/dark-mode-implementation-2

---

# 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 <head> that reads the persisted preference and sets a class or data attribute on <html> 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 <picture> with <source media="(prefers-color-scheme: dark)"> (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 <link rel="icon" media="(prefers-color-scheme: dark)" href="...">. 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 (<meta name="theme-color" content="..." media="(prefers-color-scheme: dark)">), 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 <picture> 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.

