Dark Mode & Themes

Many people prefer dark interfaces, especially at night, and operating systems let them say so. With custom properties and a few modern features — prefers-color-scheme, color-scheme and light-dark() — one stylesheet can support light and dark themes, and even let users switch.

Design tokens with variables

Try it yourself
Result

Components use semantic variables (--text, --surface) rather than raw colours. Supporting dark mode then means redefining those variables once, inside a prefers-color-scheme: dark query.

color-scheme: native parts follow too

CSS color-scheme.css
:root {
  color-scheme: light dark;
}

Declaring color-scheme tells the browser your page supports both. Built-in parts — scrollbars, form controls, the default page background — then switch automatically. (The HTML equivalent is <meta name="color-scheme" content="light dark">, which applies even before CSS loads.)

light-dark(): both values in one place

Try it yourself
Result

light-dark() picks the first value in light mode and the second in dark mode, based on the element's color-scheme. Each colour's two variants sit side by side, which is easier to maintain.

A user-selectable theme

Try it yourself
Result

Setting color-scheme to a single value forces one theme, and light-dark() follows it. A tiny script would save the choice in localStorage so it persists between visits; on many sites, a data-theme attribute on <html> is used the same way.

Designing a good dark theme

  • Don't use pure black and white. Very dark grey backgrounds and off-white text are easier on the eyes and reduce glare.
  • Desaturate and lighten accent colours. Bright, saturated colours vibrate on dark backgrounds.
  • Show elevation with lighter surfaces, not shadows — shadows barely show on dark backgrounds.
  • Check contrast in both themes; a colour that passes on white may fail on near-black.
  • Watch images and logos: transparent PNGs with dark text disappear. <picture> with a media="(prefers-color-scheme: dark)" source can swap them.

Other user preferences

CSS preferences.css
@media (prefers-contrast: more) {
  :root {
    --border: #000000;
    --muted: #333333;
  }
}

@media (forced-colors: active) {
  .button {
    border: 2px solid ButtonText;
  }
}

High-contrast preferences and Windows' forced-colours mode override your colours with the user's system palette. Make sure important boundaries use real borders (not just background colours or shadows), and use system colour keywords like ButtonText and Canvas where needed.

Try it yourself

Try it yourself
Result