How to Add Dark Mode to Your Website (CSS, Tailwind, and a Toggle)
Add dark mode the right way: follow the system setting with prefers-color-scheme, organise colours with CSS variables, use Tailwind's dark: variant, add a toggle that remembers the choice, and avoid the white flash on page load.
Dark mode used to be a nice extra. Now many people keep their whole device in dark mode, and a bright white page at night feels like a flashlight in the face. Adding it properly takes less code than you might think — if you set things up in the right order.
Step 1: Follow the system setting
Browsers tell your CSS whether the visitor's device is in dark mode with the prefers-color-scheme media query:
body {
background: white;
color: #111;
}
@media (prefers-color-scheme: dark) {
body {
background: #111;
color: #eee;
}
}
That alone gives you automatic dark mode with no JavaScript. Many sites need nothing more.
Also add this line, which tells the browser to darken its own built-in parts — scrollbars, form fields, checkboxes:
:root { color-scheme: light dark; }
Step 2: Use CSS variables for colours
Repeating colours in every rule doesn't scale. Define them once as variables, then swap the values for dark mode:
:root {
--bg: #ffffff;
--text: #111111;
--muted: #666666;
--accent: #2563eb;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0f1115;
--text: #e8e8e8;
--muted: #9a9a9a;
--accent: #60a5fa;
}
}
body { background: var(--bg); color: var(--text); }
a { color: var(--accent); }
Now every component uses var(--bg), and dark mode is one block of overrides. This is exactly how shadcn/ui themes work.
Step 3 (optional): a toggle that remembers
Some people want dark mode on a light device, or vice versa. A toggle needs three things: a class or attribute on <html>, CSS that responds to it, and a saved preference.
CSS keyed off an attribute:
:root[data-theme="dark"] {
--bg: #0f1115;
--text: #e8e8e8;
}
The toggle:
function setTheme(theme) {
document.documentElement.dataset.theme = theme;
localStorage.setItem("theme", theme);
}
Avoid the white flash
If the saved theme is applied by your app's JavaScript after it loads, dark-mode visitors see a flash of white first. Fix it with a tiny script at the very top of <head>, which runs before anything is drawn:
<script>
const saved = localStorage.getItem("theme");
const dark = saved ? saved === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
document.documentElement.dataset.theme = dark ? "dark" : "light";
</script>
In Next.js, this early script is also why theme libraries ask you to add suppressHydrationWarning to <html> — see Next.js hydration errors.
Dark mode with Tailwind CSS
In Tailwind, prefix any class with dark::
<div class="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
By default, dark: follows the system setting. For a manual toggle in Tailwind v4, add this to your CSS so dark: responds to a class on <html> instead:
@custom-variant dark (&:where(.dark, .dark *));
Then toggle the dark class on <html> with the script above (using classList instead of dataset).
Designing a good dark theme
- Don't use pure black. A very dark grey (#0f1115, #121212) is easier on the eyes and lets you show depth with slightly lighter surfaces.
- Don't use pure white text on dark backgrounds — a light grey reduces glare.
- Lighten your accent colours. A blue that works on white is often too dark on black.
- Check contrast. Muted text that's fine on white can become unreadable on dark. Aim for the contrast ratios in web accessibility basics.
- Check images and logos. A logo with dark text disappears on a dark background; provide a light version.
- Shadows don't show on dark backgrounds — use borders or lighter surfaces instead.
The summary
prefers-color-schemegives you automatic dark mode in pure CSS.- Put colours in CSS variables and override them for dark mode.
- For a toggle: set an attribute or class on
<html>, save it inlocalStorage, and apply it with an early inline script to avoid a flash. - In Tailwind, use
dark:; for a toggle in v4, add a customdarkvariant.
EasySpawn gives Claude Code a server where it can run your app and check both themes in a real browser, rather than guessing how dark mode will look. See how it works or join the waitlist.
Related: Responsive Design Basics · HTML, CSS and JavaScript Explained · What Is Tailwind CSS? · Add a Favicon to Your Website
Keep reading
What Is TypeScript? JavaScript With Labels, Explained for Beginners
TypeScript is JavaScript plus types: labels that say what kind of value each thing holds, checked before your code runs. What it looks like, why AI tools default to it, what .ts and .tsx files are, and how to read the red squiggles.
What Is a Headless CMS? And Does Your App Need One?
A headless CMS is a content editor with no website attached — it stores your content and hands it to your app through an API. How it differs from WordPress, the popular options, when it beats Markdown files or your own database, and when it's overkill.