HTML, CSS & the Browser

Dark Mode

Definition

Every current desktop and mobile operating system has a light and dark appearance setting, and browsers pass that preference to pages. A stylesheet reads it with @media (prefers-color-scheme: dark), inside which you override whatever should change. The minimal, well-behaved version pairs it with custom properties: declare --bg, --text and --accent on :root for the light theme, then redeclare those same three names inside the dark block. Nothing else in the stylesheet changes, because every rule already refers to the names rather than the colours. One more line is worth writing — color-scheme: light dark on :root tells the browser to style form controls, scrollbars and the default page background to match, which is what stops a white flash before your own CSS applies.

Why It Matters

Getting it half right is worse than not doing it. A page that sets a dark background but leaves a hard-coded #333 on one section produces black text on near-black, which is unreadable rather than merely ugly. Contrast has to be rechecked in both themes: a mid-grey that passes against white will usually fail against a dark background, and the WCAG minimum of 4.5 to 1 for body text applies to each theme separately. Pure white text on pure black is also a poor default — it smears at small sizes, and something around #e8e8e8 on #121212 reads better. Images need a look too, since a logo saved as black-on-transparent disappears entirely.

How It Works

The media query evaluates the user's system setting, so it needs no toggle, no script and no stored preference to work — and it updates live if the visitor switches appearance while the page is open. Because it is a media query, rules inside it add no specificity and win only by coming later in the file, so the dark block goes after the light declarations. A manual switch is a separate feature layered on top: a script sets an attribute such as data-theme="dark" on the html element, the stylesheet has a matching :root[data-theme="dark"] selector redeclaring the same variables, and the choice is kept in localStorage. Testing does not need two machines — developer tools has a rendering option to emulate prefers-color-scheme, and on macOS or Windows the system appearance setting flips the real thing in a second.

Real-World Example

A developer publishes API documentation at ledger-api.99helpers.site, originally light only. Adding it takes twelve lines: three custom properties on :root, one prefers-color-scheme block redeclaring them, and color-scheme: light dark. Two fixes fall out of the first read-through — a code block with a hard-coded #f5f5f5 background, and an inline SVG diagram drawn in black strokes that needed currentColor instead. The published file grows by under a kilobyte.

Common Mistakes

  • Setting a dark background while some colours stay hard-coded in the rules — anything not routed through a variable keeps its light-theme value, and that is where the unreadable text comes from
  • Assuming contrast carries over between themes — a colour that passes 4.5 to 1 against white commonly fails against a dark background, so each theme needs checking on its own
  • Building a manual toggle first and skipping prefers-color-scheme — most visitors never touch a toggle, and the system setting already tells you what they want

Related Terms

Put a file online in seconds

Drop in a document, an image, a page or a whole static website and share the link — free, with no build step and no server to set up.

Host a file free →