Adding dark mode with prefers-color-scheme
The simplest dark mode is the one the browser can choose for you. The prefers-color-scheme media feature reads the visitor’s light or dark system preference and lets CSS respond to it.
body {
background-color: #fff;
color: #333;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #333;
color: #fff;
}
}
Keep the light theme as the default, then override only what changes in dark mode. The preference may come from the operating system or the browser.
The media feature has three values:
no-preference: no preference is known.light: the user prefers a light interface.dark: the user prefers a dark interface.
If JavaScript behaviour also depends on the colour scheme, matchMedia() can read it. You can also check whether the browser recognises the media feature:
if (window.matchMedia('(prefers-color-scheme)').media === 'not all') {
console.log('Browser doesn't support dark mode');
}
Custom properties keep the theme manageable
For anything beyond two colours, repeating overrides across many selectors gets tedious. CSS custom properties keep the theme values together:
:root {
--page-background: #fff;
--page-title: #333;
--page-text: #333;
}
@media screen and (prefers-color-scheme: dark) {
:root {
--page-background: #333;
--page-title: #fff;
--page-text: #fff;
}
}
body {
background: var(--page-background);
color: var(--page-text);
}
h1 {
color: var(--page-title);
}
p {
color: var(--page-text);
}
This also gives you a clean way to add a manual theme switch. Toggle an attribute on <body> and override the same properties inside it:
body[theme="dark"] {
--page-background: #333;
--page-title: #fff;
--page-text: #fff;
}
For a simple site, inheriting color and using currentColor for borders or icons can save more CSS. Do not blindly invert everything, though. Images, shadows, focus indicators, and muted text may all need separate attention.
Dark mode inside an SVG
An SVG can use the same media query in an embedded <style> block:
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<style>
circle {
fill: white;
stroke: black;
stroke-width: 3px;
}
@media (prefers-color-scheme: dark) {
circle {
fill: black;
stroke: yellow;
}
}
</style>
<circle cx="50" cy="50" r="47"/>
</svg>
Dark mode is a preference, not a cure
Dark interfaces can use less energy on OLED and AMOLED displays, and some people find them more comfortable at night. They are not always easier on the eyes; in bright conditions, light text on a dark background can be harder to read.
That is why I prefer following the user’s existing setting first. A manual switch is a useful extra, but the system preference gives the site a sensible default with almost no code.