Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Sale
Philips 24 Inch Computer Monitor FHD 100Hz VA VESA Flicker-Free, 241V8LB
  • 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Philips 22 Inch Computer Monitor FHD 100Hz VA VESA Flicker-Free, 221V8LB
  • 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #3
Sale
Dell 24 Monitor - SE2426H - 23.8-inch FHD (1920x1080) 144Hz 1ms Display, in-Plane Switching (IPS) Technology, AMD FreeSync™, TÜV 3-Star 2X HDMI, Tilt
  • 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Sale
Samsung 27" Essential S3 (S36GD) Series FHD 1800R Curved Computer Monitor
  • 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
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
Sale
Sceptre New 22-Inch Gaming Monitor, FHD 1080p, Up to 144Hz, HDMI, DisplayPort, Built-in Speakers, Machine Black (E225W-FW144 Series, 2026)
  • 【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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.