How do I add dark mode to a website?
Put every colour in a custom property, redefine those properties inside @media (prefers-color-scheme: dark), and add a data-theme attribute for a manual toggle.
#1. Colours become variables
:root {
--page: #f4f9ff;
--surface: #ffffff;
--ink: #0c1c30;
--ink-muted: #546a92;
--line: #d7e7fa;
}Components only ever reference the variables:
.card {
background: var(--surface);
color: var(--ink);
border: 1px solid var(--line);
}#2. Redefine them for dark
:root[data-theme='dark'] {
--page: #070e1b;
--surface: #0f1c30;
--ink: #e9f2ff;
--ink-muted: #8fa8ca;
--line: #1f3454;
}Not one component rule changes. This is the whole argument for custom properties.
#3. Respect the OS, allow an override
<script>
(function () {
try {
var stored = localStorage.getItem('theme');
var prefersDark = matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.dataset.theme = stored || (prefersDark ? 'dark' : 'light');
} catch (e) {}
})();
</script>Put this inline in <head>, before the stylesheet renders. An external or deferred script runs after first paint, which produces a white flash on every page load.
#Things that catch people out
Do not use pure black. #000 under white text causes halation that makes reading tiring. A very dark desaturated blue reads as black and is far more comfortable.
Pull white text back too. Pure white on dark is usually too bright; tint it slightly toward the background hue.
Shadows need rethinking. A soft grey shadow is invisible on a dark surface. Use a darker, more opaque shadow, or switch to a subtle border.
Images may need it. Screenshots of light UIs look like holes in a dark page. filter: brightness(.85) helps, or ship both.
Tell the browser. <meta name="color-scheme" content="light dark"> makes form controls and scrollbars match.