Dark mode for any website — 4 CSS approaches
Your OS has had dark mode for years. Your browser supports it. Most apps respect it. Then you open some documentation, a blog or a 2014 intranet — and get hit in the face with 100% white background at 11pm. Four methods to fix this once and for all.
Why this problem exists at all
The prefers-color-scheme CSS standard has been in Chrome since 2019 and Safari since 2018. Your OS knows your preference, the browser propagates it, sites should respect it. Should — in practice ~40% of sites ignore it. Reasons: legacy codebase, no budget for a second theme, marketing that wants "brand consistency", the author hasn't gotten to it yet.
If the author won't do it for you — do it yourself. Four methods, in order of increasing elegance.
Method 1 — universal filter (works on anything)
One snippet, fits any site, no analysis of the specific CSS required:
html {
filter: invert(0.92) hue-rotate(180deg);
background: #1a1a1a;
}
/* Re-invert images, video and iframes so they look
natural — otherwise your friends' selfies look like
the green-skin filter. */
img, video, picture, iframe, svg,
[style*="background-image"] {
filter: invert(1) hue-rotate(180deg);
}
Trick: invert(0.92) instead of full invert(1) gives a softer black than a hard white→black swap — easier on the eyes. hue-rotate(180deg) corrects colours so orange stays orange (not turn into blue).
Pros: works everywhere, zero analysis.
Cons: drop shadows, gradients, subtle gray tones look weird. Some fixed elements (sticky headers) can desync during scroll.
Method 2 — force prefers-color-scheme: dark
Sites that have dark mode but only respond to OS-level preference. What if you keep the OS in light (because you're designing in Figma) but want GitHub docs dark? Override matchMedia:
// Force prefers-color-scheme: dark via JS
const dark = window.matchMedia('(prefers-color-scheme: dark)');
Object.defineProperty(dark, 'matches', {
get: () => true,
configurable: true,
});
// Notify listeners (sites attached via addEventListener)
dark.dispatchEvent(new Event('change'));
This works on any site using window.matchMedia('(prefers-color-scheme: dark)') for detection — all modern React/Vue apps. Won't work on sites that detect once at load (you'd have to refresh), or on static CSS without JS detection.
Method 3 — override CSS custom properties
Sites built on CSS variables (~60% of modern webapps) typically have theme tokens like --bg-primary, --text-primary. Open DevTools, find the names, override:
/* Example: Stripe Dashboard, where everything is variable-based */
:root {
--color-canvas-default: #0d1117 !important;
--color-canvas-subtle: #161b22 !important;
--color-fg-default: #c9d1d9 !important;
--color-fg-muted: #8b949e !important;
--color-border-default: #30363d !important;
}
Pros: precise, doesn't mess up images or shadows.
Cons: 5 minutes of digging in DevTools to find variable names. The site must use CSS vars (check: document.documentElement.style in the console).
Method 4 — dedicated per-site dark theme
For sites you spend hours every day on — 30 minutes for a tailored theme is worth it. Selective override:
/* Your generic corp intranet */
body, .page-content, .sidebar, .top-bar {
background: #1a1a1a !important;
color: #e0e0e0 !important;
}
.card, .panel, .modal {
background: #242424 !important;
border-color: #333 !important;
}
a, a:visited { color: #58a6ff !important; }
a:hover { color: #79c0ff !important; }
input, textarea, select {
background: #1a1a1a !important;
color: #e0e0e0 !important;
border-color: #444 !important;
}
/* Tables — most critical for readability */
table th { background: #2d2d2d !important; color: #fff !important; }
table tr:nth-child(odd) { background: #1f1f1f !important; }
table tr:nth-child(even) { background: #1a1a1a !important; }
Start from the main containers (body, page), drill down to components (cards, modals), end with form elements and tables. Without !important you usually can't win — the site has its own styles at higher specificity.
Common pitfalls
- Inline
background-image(e.g.style="background: url(...)") — method 1's filter won't always catch it. Add[style*="background-image"]separately. - Drop shadows on dark background look like halos. Either remove (
box-shadow: none) or replace with a lighter accent. - Brand colours (logos, coloured icons) lose recognition after invert. Add exceptions:
.brand-logo { filter: invert(1) hue-rotate(180deg) !important; }. - Forms often have
color: blackhardcoded inline. Override explicitly:input { color: #e0e0e0 !important; }. - Sticky / fixed elements with method 1's filter may desync during scroll. Usually acceptable, but worth knowing.
How to plug into JustZix
- Install JustZix (2 minutes).
- Create a folder called "Dark mode".
- Rule "Dark mode everywhere": URL pattern
*, CSS = Method 1. Set it inactive by default — toggle on when you need it. - Rule per favourite site: pattern
https://myapp.com/*, CSS = Method 3 or 4 (with specific tokens / selectors). Always active. - Toggle the whole folder with one click on the floating button — light/dark in one move.
What's next
The same hierarchy (universal → precise per-site) applies in other rule categories — see Examples and Use cases. Dark mode is just the most obvious case.
Install JustZix for free and finally control how the sites you stare at for 8 hours a day look.
Rate this post
No ratings yet — be the first.