Use CSS as the baseline and JavaScript only for an explicit override. Define light and dark design tokens, select the system palette with prefers-color-scheme, and declare color-scheme: light dark so browser-owned controls can match. If you add a switch, store only light or dark, initialize that value before the first paint, and let an unset value continue following the operating system.
The implementation in one sentence
A robust dark-mode switch has three layers: CSS custom properties for every theme-sensitive color, a media query for the no-JavaScript system default, and a small override script that applies a saved choice before rendering. The page should remain usable if JavaScript is blocked or local storage is unavailable.
Choose the behavior you actually want
| Approach | User control | System changes | Persistence | JavaScript | First-paint risk |
|---|---|---|---|---|---|
| System-following only | Controlled by OS or browser | Updates automatically | Provided by the system | None | Low when CSS is available |
| System plus manual override | Per-site light or dark choice | Ignored while a choice is pinned | localStorage |
Required for the switch and persistence | Low with an early initializer; high if applied after page load |
Use the first option for a simple, resilient site. Use the second when visitors need a choice independent of their operating-system setting. Keep the system behavior as the fallback rather than making JavaScript a requirement for readable content.
Build a CSS-only system-aware theme
1. Advertise supported schemes in the head
Put the metadata before your stylesheet links. The first listed scheme is preferred and the second is acceptable; only dark is invalid.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstall#1 Best Overall
- CRISP CLARITY: This 23.8″ Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
- INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
- THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors
- WORK SEAMLESSLY: This sleek monitor is virtually bezel-free on three sides, so the screen looks even bigger for the viewer. This minimalistic design also allows for seamless multi-monitor setups that enhance your workflow and boost productivity
- A BETTER READING EXPERIENCE: For busy office workers, EasyRead mode provides a more paper-like experience for when viewing lengthy documents
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="color-scheme" content="light dark">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<title>Theme example</title>
</head>
The metadata tells the user agent that both schemes are supported. The CSS declaration later makes the same promise to the rendering engine and lets controls such as native form widgets and scrollbars choose an appropriate appearance.
2. Define paired color tokens
:root {
color-scheme: light dark;
--page-bg: #ffffff;
--surface: #f4f6f8;
--text: #1b1f23;
--muted-text: #5b6570;
--border: #c8d0d8;
--accent: #0759c7;
--accent-contrast: #ffffff;
}
@media (prefers-color-scheme: dark) {
:root {
--page-bg: #101317;
--surface: #1a2027;
--text: #f2f5f7;
--muted-text: #b4bec8;
--border: #3c4652;
--accent: #8ab4ff;
--accent-contrast: #08101d;
}
}
html, body {
background: var(--page-bg);
color: var(--text);
}
.card {
background: var(--surface);
border: 1px solid var(--border);
}
a { color: var(--accent); }
.button {
background: var(--accent);
color: var(--accent-contrast);
}
Define foreground and background together. “Light” and “dark” are palette families, not guaranteed contrast values, so check normal text, large text, controls, borders, links, focus indicators, images, charts and code blocks in both modes. Author-styled components do not automatically repaint merely because color-scheme is present.
3. Let authored components inherit the scheme
Avoid hard-coded white backgrounds in individual components. Replace them with tokens, and set SVG fills, shadows and code-block colors explicitly where inheritance does not reach them. For images containing text, provide a suitable asset or treatment; a dark page cannot fix a low-contrast image.
Add a manual switch without losing the system fallback
Use an explicit state model
Use three states: no saved value (system), light, and dark. The HTML attribute data-theme is convenient because CSS can override tokens only when a pinned value exists.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesRank #2
- CRISP CLARITY: This 22 inch class (21.5″ viewable) Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
- 100HZ FAST REFRESH RATE: 100Hz brings your favorite movies and video games to life. Stream, binge, and play effortlessly
- SMOOTH ACTION WITH ADAPTIVE-SYNC: Adaptive-Sync technology ensures fluid action sequences and rapid response time. Every frame will be rendered smoothly with crystal clarity and without stutter
- INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
- THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors
/* Keep the media-query baseline above. These rules win only for a pinned choice. */
:root[data-theme="light"] {
color-scheme: light;
--page-bg: #ffffff;
--surface: #f4f6f8;
--text: #1b1f23;
--muted-text: #5b6570;
--border: #c8d0d8;
--accent: #0759c7;
--accent-contrast: #ffffff;
}
:root[data-theme="dark"] {
color-scheme: dark;
--page-bg: #101317;
--surface: #1a2027;
--text: #f2f5f7;
--muted-text: #b4bec8;
--border: #3c4652;
--accent: #8ab4ff;
--accent-contrast: #08101d;
}
Initialize a saved choice before first paint
Place this short inline script immediately after the color-scheme meta element and before external CSS when possible. Invalid storage values are ignored, preserving the CSS system default.
<script>
(() => {
try {
const saved = localStorage.getItem('theme');
if (saved === 'light' || saved === 'dark') {
document.documentElement.dataset.theme = saved;
}
} catch (_) {
// Storage may be blocked; keep the system-controlled CSS theme.
}
})();
</script>
This prevents a page from painting in the system mode and then visibly switching to a saved mode after JavaScript runs.
Make the control operable and stateful
<button id="theme-toggle"
type="button"
aria-label="Change color theme"
aria-pressed="false">
Toggle theme
</button>
<script>
(() => {
const root = document.documentElement;
const button = document.querySelector('#theme-toggle');
const media = window.matchMedia('(prefers-color-scheme: dark)');
function savedTheme() {
try {
const value = localStorage.getItem('theme');
return value === 'light' || value === 'dark' ? value : null;
} catch (_) { return null; }
}
function apply(theme) {
if (theme) root.dataset.theme = theme;
else delete root.dataset.theme;
if (button) {
const pinned = Boolean(theme);
button.setAttribute('aria-pressed', String(pinned));
button.textContent = pinned ? `Use system (${theme})` : 'Use dark mode';
}
}
apply(savedTheme());
button?.addEventListener('click', () => {
const current = savedTheme();
const systemIsDark = media.matches;
const next = current
? (current === 'dark' ? 'light' : 'dark')
: (systemIsDark ? 'light' : 'dark');
try { localStorage.setItem('theme', next); } catch (_) {}
apply(next);
});
media.addEventListener('change', () => {
// A pinned choice remains pinned. The system can update only the unset state.
if (!savedTheme()) apply(null);
});
})();
</script>
If you want a three-choice menu instead of a two-state toggle, expose “System”, “Light” and “Dark” as separate buttons or radio options. Set the selected option with aria-checked or aria-pressed, give every control an accessible name, and keep a visible keyboard focus outline in both palettes.
Use light-dark() only with a fallback
The light-dark() function can select two values from the current color scheme, but retain the custom-property and media-query implementation for browsers that do not support it. Current Chrome guidance lists it as Baseline from 2024-05-13 (Chrome and Edge 123, Firefox 120 and Safari 17.5). Compatibility data changes, so do not make that function your only path.
Rank #3
- Clear visuals. Fluid motion: A 144Hz refresh rate and 1ms MPRT deliver smooth, tear‑free motion across work, gaming, and streaming for clearer, more fluid viewing.
- Eye comfort: TÜV Rheinland 3‑star* certification reduces harmful blue light while preserving stunning color quality without compromise. *TÜV Rheinland 3-star eye comfort certification.
- Wide viewing angle: Get consistent views across a wide 178° /178° viewing angle.
- In-Plane Switching (IPS): See excellent color accuracy and consistency across wide viewing angles with In-plane Switching (IPS) technology.
- Ultra-thin bezels: Maximize your viewing experience with thin bezels.
:root {
color-scheme: light dark;
--page-bg: #fff; /* fallback */
}
@media (prefers-color-scheme: dark) {
:root { --page-bg: #101317; }
}
@supports (color: light-dark(white, black)) {
:root { --page-bg: light-dark(#fff, #101317); }
}
Accessibility checks before shipping
- Verify text and interactive controls remain legible against their actual backgrounds in both modes.
- Do not communicate state with color alone; retain labels, icons or text.
- Test keyboard operation, visible focus and screen-reader naming for the switch.
- Check disabled, error, selected and visited states, not just the default button.
- Test charts, syntax highlighting, SVGs, shadows, borders and third-party widgets.
- Remember that a switch is only an alternate-presentation technique. Its presence does not itself establish WCAG conformance; the resulting presentation must meet the claimed level.
System changes, storage and privacy edge cases
When the operating system changes
Listen to matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ...). Update the page only when no manual value is saved. This avoids unexpectedly undoing a visitor’s deliberate choice.
When storage is unavailable
Private browsing policies, disabled cookies or a blocked storage API can make localStorage throw. Wrap reads and writes in try...catch; the media-query baseline still works for that visit.
When content is inside an iframe
Browser behavior differs. Chrome and Firefox pass the embedding element’s color scheme into iframes, while Safari resolves prefers-color-scheme to the system setting inside the frame. If you control both documents, send an explicit theme through a URL parameter or postMessage, then validate the receiving page’s origin and message data.
Compatibility expectations
Google Chrome’s modern guidance lists color-scheme as Baseline from 2022-02-03, with Chrome 98, Edge 98, Firefox 96 and Safari 13 as supporting releases. Treat it as progressive enhancement and keep prefers-color-scheme rules for older browsers. In browsers without these features, your default token values should still provide a readable light presentation.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #4
- CURVED FOR ENHANCED ENGAGEMENT: An immersive viewing experience with a curved monitor that wraps more closely around your field of vision; It creates a wider view, enhancing depth perception and minimizing peripheral distraction
- SMOOTH PERFORMANCE FOR SEAMLESS CONTENT: Stay in the action when playing games, watching videos, or working on creative projects; The 100Hz refresh rate reduces lag and motion blur so you don't miss a thing in fast-paced moments¹
- MORE GAMING POWER: Gain the edge with optimizable game settings; Color and image contrast can be adjusted to see scenes more vividly and spot enemies hiding in the dark; Game Mode adjusts any game to fill the screen so you can view every detail²
- KEEP IT EASY ON THE EYES: Care for your eyes and stay comfortable, even during long sessions; Advanced eye comfort technology certified by TÜV reduces eye strain by minimizing blue light and reducing irritating screen flicker²
- INCREASED VERSATILITY: Connect to more; Plug devices straight into your monitor for increased flexibility, making your computing environment even more convenient
Troubleshooting
The switch changes text but not form controls
Set color-scheme on the root and ensure the selected override changes it to exactly light or dark. Native controls are browser-owned; author CSS still controls custom controls.
The page flashes the wrong theme
Move the color-scheme meta element and saved-theme initializer earlier in the head, before blocking stylesheets. Do not wait for DOMContentLoaded to apply the saved value.
The operating-system change no longer updates the page
Check that no data-theme attribute or theme storage value remains. A pinned value is intentionally higher priority than the media query.
The theme resets on every visit
Inspect storage exceptions, origin changes between HTTP and HTTPS, and code that clears storage. Keep the key and accepted values stable.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Best Value
- 【INTEGRATED SPEAKERS】Whether you're at work or in the midst of an intense gaming session, our built-in speakers provide rich and seamless audio, all while keeping your desk clutter-free.
- 【EASY ON THE EYES】 Protect your eyes and enhance your comfort with Blue-Light Shift technology. This feature reduces harmful blue light emissions from your screen, helping to alleviate eye strain during long hours of use and promoting healthier viewing habits.
- 【WIDEN YOUR PERSPECTIVE】Our sleek minimal bezel design ensures undivided attention. The nearly bezel-free display seamlessly connects in a dual monitor arrangement, delivering an unobstructed view that lets you focus on more at once, completely distraction-free.
Dark mode is unreadable in one component
Find a hard-coded color, image, inline SVG fill or shadow. Convert it to a token and evaluate the foreground/background pair, including focus and error states.
Or skip the browser setup
If your immediate goal is a clean screenshot of a themed page rather than implementing the theme yourself, ScreenshotNeo captures a URL with one request. It accepts cookie and consent banners before capture, removes more than 60 known consent platforms plus newsletter popups and chat widgets, and bills only clean shots. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result.
It also provides an MCP server for AI agents such as Claude, Cursor and other MCP clients, with take_screenshot, get_page_info and capture_pdf tools. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000. Every feature is included on every plan.
One-call example
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the complete parameter reference in the ScreenshotNeo documentation. The same request in Python:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
And in Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Start with 1,000 free screenshots a month—no card required.
Frequently Asked Questions
Should I store the complete palette in localStorage?
No. Store only the explicit mode, such as light or dark; keep colors in CSS so they can be updated centrally.
Can a dark-mode switch prove WCAG compliance?
No. Each resulting presentation must be evaluated for contrast, focus visibility, keyboard use and other applicable requirements.
What should happen when a user chooses System?
Remove the saved value and the data-theme attribute so the prefers-color-scheme media query controls the page again.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

