How do you add dark mode to a website without a JavaScript library?
You can add dark mode to any website with CSS custom properties and about fifteen lines of JavaScript — no library required. Define your colors as variables, swap them with a
data-themeattribute on the root element, respect the user’s system preference, and remember their choice. That’s the whole pattern, and it works in plain HTML, React, or any framework.
Learn how to add dark mode to any website properly.
Step 1 — Define colors as CSS variables
The trick to clean dark mode is never hard-coding colors. Declare them once, reference them everywhere:
:root{
--bg:#ffffff; --text:#1a1a1a; --card:#f4f4f5; --accent:#4a9eff;
}
[data-theme="dark"]{
--bg:#0e1116; --text:#e8ecf3; --card:#1a1f2b; --accent:#6cb0ff;
}
body{ background:var(--bg); color:var(--text); }
.card{ background:var(--card); }
Now switching themes is a matter of changing one attribute — every element using the variables updates instantly.
Step 2 — Add the toggle (vanilla JS)
const toggle = document.querySelector('#theme-toggle');
toggle.addEventListener('click', () => {
const root = document.documentElement;
const next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
root.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
});
Step 3 — Respect system preference and avoid the “flash”
Two details separate a polished implementation from a janky one. First, default to the user’s OS setting. Second, apply the saved theme before the page paints to prevent a white flash on load. Put this small script in the <head>file before your stylesheet:
<script>
const saved = localStorage.getItem('theme');
const prefersDark = matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.setAttribute(
'data-theme', saved || (prefersDark ? 'dark' : 'light')
);
</script>
Running this inline and early is what eliminates the flash-of-wrong-theme that plagues most tutorials.
The React version
import { useEffect, useState } from 'react'
function useTheme(){
const [theme,setTheme] = useState('light')
useEffect(()=>{
const saved = localStorage.getItem('theme')
|| (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark':'light')
setTheme(saved)
document.documentElement.setAttribute('data-theme', saved)
},[])
const toggle = () => {
const next = theme === 'dark' ? 'light' : 'dark'
setTheme(next)
document.documentElement.setAttribute('data-theme', next)
localStorage.setItem('theme', next)
}
return { theme, toggle }
}
(In Next.js, run the early no-flash script via a small inline script in your root layout, since localStorage isn’t available during server rendering.)
Accessibility & polish checklist: Add dark mode to website
- Keep contrast ≥ 4.5:1 in both themes — dark mode often fails on muted grays.
- Give the toggle an
aria-label(“Switch to dark mode”). - Add
color-scheme: light darkSo form controls and scrollbars adapt too. - Dim images slightly in dark mode with a subtle filter if they look too bright.
This pattern is exactly how well-built templates ship theming — clean variables, no heavy dependency. Every DesignToCodes template that supports dark mode uses this approach so you can extend it without fighting a framework.
Frequently asked questions
How do you add dark mode without a library?
Define your colors as CSS custom properties, override them under a [data-theme="dark"] selector, and toggle that attribute with about fifteen lines of JavaScript. No framework or library is needed.
How do I stop the white flash when the page loads in dark mode?
Apply the saved theme with a tiny inline script <head> before your stylesheet loads, so the correct theme is set before the first paint.
Should dark mode follow the system setting?
Default to it using prefers-color-scheme, but let users override and remember their choice in localStorage. Respecting the OS preference first is the expected behavior.
Does dark mode affect SEO?
Not directly, but it improves user experience and time on page, and using it color-scheme correctly avoids accessibility issues that can indirectly matter. It’s a UX win more than a ranking one.
Want theming done right out of the box? Browse DesignToCodes templates with a clean, variable-based dark mode built in.





