Mastering Dark Mode: Hand‑Crafted CSS & JavaScript Techniques
This article walks through practical ways to implement true dark mode on web pages—including manual toggles, system‑preference detection, CSS variables, stylesheet swapping, filter tricks, and common pitfalls—while providing complete code examples and explanations.
Implementing a genuine dark mode often requires hand‑written code rather than a simple overlay. Mobile QQ recently demanded proper dark‑mode support, prompting a full rewrite of the theme logic.
Common Practices
Before coding, the author recommends reading CSS‑Tricks’ A Complete Guide to Dark Mode on the Web for a solid overview.
Activation Methods
There are two main ways to activate dark mode: a manual switch that toggles a class on the page, or automatic detection based on the system or application preference.
Manual Toggle
Attach an event handler to a toggle button that adds or removes a dark-theme class on the body element.
const btn = document.querySelector('.btn-toggle');
btn.addEventListener('click', function() {
document.body.classList.toggle('dark-theme');
});Two CSS approaches are shown.
/* Method 1: Separate styles */
body {
color: #222;
background: #fff;
a { color: #0033cc; }
}
body.dark-theme {
color: #eee;
background: #121212;
a { color: #809fff; }
} /* Method 2: CSS variables */
body {
--text-color: #222;
--bkg-color: #fff;
--anchor-color: #0033cc;
}
body.dark-theme {
--text-color: #eee;
--bkg-color: #121212;
--anchor-color: #809fff;
}
body {
color: var(--text-color);
background: var(--bkg-color);
a { color: var(--anchor-color); }
}Another option is to swap entire stylesheet files.
<html lang="en">
<head>
<link href="light-theme.css" rel="stylesheet" id="theme-link">
</head>
</html> const btn = document.querySelector('.btn-toggle');
const theme = document.querySelector('#theme-link');
btn.addEventListener('click', function() {
if (theme.getAttribute('href') == 'light-theme.css') {
theme.href = 'dark-theme.css';
} else {
theme.href = 'light-theme.css';
}
});Follow System
Use the prefers-color-scheme media query or JavaScript detection to match the OS theme.
/* Direct media query */
@media (prefers-color-scheme: dark) { }
@media (prefers-color-scheme: light) { } if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.body.classList.add('dark-theme');
} else {
document.body.classList.remove('dark-theme');
}User Preference
Persist a user’s choice in localStorage (or cookies for server‑rendered pages) so the site remembers the selected theme across visits.
Tricks
A one‑line CSS filter can produce a quick dark mode, though it has limitations.
html {
filter: invert(1) hue-rotate(180deg);
}Explanation: invert() flips color channels, while hue-rotate() rotates the hue wheel. The article shows numeric examples of how colors change after inversion.
Images also get inverted, so they must be re‑inverted to appear normal.
html {
filter: invert(1) hue-rotate(180deg);
img {
filter: invert(1) hue-rotate(180deg);
}
}Dark Mode Pitfalls
Reverse‑Engineering Target Colors
When using filter, start from a light base color and let the filter produce the desired dark shade. Determining the original light color from a design’s dark color can be done by applying the filter to a test element and reading the resulting color.
Background Image Inversion Issue
Filters do not affect elements styled with background‑image, and such elements cannot be selected via img. The workaround is to replace background images with img tags or use pseudo‑elements, though both have drawbacks.
Filter Affects Fixed Elements
Applying filter to a non‑root element creates a new containing block, breaking position: fixed and position: absolute elements. Apply the filter to the html element or wrap fixed components in a separate container.
Server‑Rendered Pages Missing Dark‑Mode Class
When a page is rendered on the server, window.matchMedia is unavailable, so the initial HTML may lack the dark-mode class. After hydration, the client‑side script must add the class based on the stored preference; otherwise components render with the wrong theme.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tencent IMWeb Frontend Team
IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
