How to add dark mode to your site — in 3 steps, no libraries

It feels like dark mode means redrawing your whole site, just in darker tones. It doesn't. Modern CSS does almost all the work for you with two mechanisms, and you can add it in one evening without a single library.
More than that: the browser already knows which theme the user wants — light or dark, it took that from the system settings. Your job isn't to guess, but to answer that request. Let's go step by step.
Step 1. Move colors into variables
The problem with dark mode is that colors are scattered all over your code. First we gather them in one place — into CSS variables. Declare them in :root (that's the document root, reachable everywhere):
:root {
--bg: #ffffff; /* background */
--text: #141416; /* text */
}
body {
background: var(--bg);
color: var(--text);
}
Now the whole site is colored not by hardcode but through var(--bg) and var(--text). Change a variable in one place — it changes everywhere. That's the foundation: we'll switch the theme by changing just these values.
Step 2. Add a dark set via a media query
Here's the magic that knows the user's choice. The media query prefers-color-scheme: dark fires when the system has dark mode on. Inside it, just redefine the same variables:
@media (prefers-color-scheme: dark) {
:root {
--bg: #141416;
--text: #f5f5f0;
}
}
That's it. Nothing else to touch — the site already adapts to the system. For someone with dark mode on their phone, your site opens dark by itself, no buttons. Test it: flip the theme in your OS settings and refresh the page.
Step 3 (optional). A toggle button
Auto-adapting is 80% of the value. If you want to give a manual toggle, add one more set of colors via a data-theme attribute and switch it with a drop of JS:
[data-theme="dark"] {
--bg: #141416;
--text: #f5f5f0;
}
// on click, add/remove dark theme and remember the choice
const btn = document.querySelector("#theme-toggle");
btn.addEventListener("click", () => {
const isDark = document.documentElement.getAttribute("data-theme") === "dark";
document.documentElement.setAttribute("data-theme", isDark ? "light" : "dark");
localStorage.setItem("theme", isDark ? "light" : "dark");
});
localStorage is the browser's memory: the choice sticks, and on the next visit the site opens in the theme the person picked by hand.
What you get
A site that opens dark by itself for people who like dark — and has its own toggle for the rest. No libraries, on plain CSS plus a few lines of JS.
Common mistake: "I changed it and it doesn't work"
Usually one of two causes. Either colors are still hardcoded past the variables — then change them to var(...). Or the styles aren't picked up at all — that's a separate issue, why CSS isn't working, with its own checklist. Dark mode lives in the DOM as ordinary styles, so the debugging rules are the same.
Do I need a library for dark mode?
For the basic version — no, everything above is plain CSS. Toggle libraries are handy in big frameworks where you need to sync the theme across pages, but for a regular landing page that's overkill — two steps are enough.
What if I use React or another framework?
Same mechanics — variables plus prefers-color-scheme. Only how data-theme gets attached changes: via component state instead of manual JS. The essence — switch variables, don't rewrite colors — stays.
Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.





