Skip to content
Laviyx Learn

Try

CSS course menu

Dark Mode in CSS: prefers-color-scheme, color-scheme, light-dark() and Theme Toggles

Build dark mode with CSS: the prefers-color-scheme media query, theming with custom properties, the color-scheme property for built-in controls, the light-dark() function, a manual theme toggle, and other user preferences such as reduced motion and contrast.

Advanced7 min readUpdated
On this page

Many people set their phone or computer to a dark theme. Websites can follow that preference automatically and also offer a manual switch. Custom properties make this straightforward.

Following the system setting#

The prefers-color-scheme media feature tells you whether the user chose a light or dark theme:

CSS
:root {
  --bg: #ffffff;
  --text: #1f2328;
  --muted: #59636e;
  --card: #f6f8fa;
  --link: #0969da;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0d1117;
    --text: #e6edf3;
    --muted: #9198a1;
    --card: #161b22;
    --link: #4493f8;
  }
}

body { background: var(--bg); color: var(--text); }
.card { background: var(--card); }
a { color: var(--link); }

Every rule reads the variables; only the variable values change between themes. This is far easier to maintain than writing every rule twice.

color-scheme: built-in controls#

Scrollbars, form fields, checkboxes and the default page background are drawn by the browser. Tell it which schemes your page supports so it draws them to match:

CSS
:root {
  color-scheme: light dark;
}

Without this, a dark page can end up with bright white text inputs and scrollbars.

light-dark()#

With color-scheme set, the light-dark() function picks a value for the current scheme in a single declaration:

CSS
:root { color-scheme: light dark; }
body {
  background: light-dark(#ffffff, #0d1117);
  color: light-dark(#1f2328, #e6edf3);
  font-family: system-ui, sans-serif;
}
HTML
<p>This preview follows the light or dark setting of the page around it.</p>
<p><input placeholder="Built-in controls match too"></p>
Result

The preview frame takes its preference from this page, so it turns dark when this site is in dark mode (use the theme button in the header to try it). light-dark() is supported in current versions of all major browsers; for older browsers, use the media query approach.

A manual toggle#

Some users want a different theme on your site than on their system. A common pattern stores the choice as an attribute on <html>:

CSS
:root { --bg: #ffffff; --text: #1f2328; --card: #f1f3f5; color-scheme: light; }
:root[data-theme="dark"] { --bg: #0d1117; --text: #e6edf3; --card: #21262d; color-scheme: dark; }

body { background: var(--bg); color: var(--text); font-family: system-ui, sans-serif; transition: background 0.2s; }
.card { background: var(--card); padding: 12px; border-radius: 8px; }
HTML
<button type="button" id="toggle" aria-pressed="false">Dark theme</button>
<div class="card"><p>Press the button to switch themes.</p></div>
JavaScript
const button = document.getElementById("toggle");
button.addEventListener("click", () => {
  const dark = document.documentElement.dataset.theme !== "dark";
  document.documentElement.dataset.theme = dark ? "dark" : "light";
  button.setAttribute("aria-pressed", String(dark));
});
Result

On a real site, you would also save the choice (for example in localStorage) and apply it before the page is first drawn, with a small inline script in the <head>, to avoid a flash of the wrong theme.

Designing a good dark theme#

  • Do not simply invert colours. Use very dark greys (such as #0d1117 or #121212) rather than pure black, and off-white text rather than pure white, to reduce glare.
  • Reduce the saturation of bright brand colours, which can look harsh on dark backgrounds.
  • Shadows are hard to see on dark backgrounds; use slightly lighter surfaces for raised elements instead.
  • Recheck contrast for every colour pair in both themes.
  • Images with white backgrounds can be glaring; consider lowering their brightness slightly in dark mode.

Other user preferences#

Media featureRespond by
prefers-reduced-motion: reduceTurning off large movements, parallax and autoplaying animation
prefers-contrast: moreStrengthening borders and text contrast
forced-colors: activeWindows High Contrast and similar modes override your colours; make sure borders and focus outlines still show
prefers-reduced-transparency: reduceReplacing see-through and blurred backgrounds with solid ones (newer, limited support)

Practice#

Practice exercise

Write variables for a light theme with a white background, dark text and a blue accent, then a dark version under prefers-color-scheme: dark. Set color-scheme so form controls match, and style a button with the accent colour.

Show solution
CSS
:root {
  color-scheme: light dark;
  --bg: #ffffff;
  --text: #1f2328;
  --accent: #2f5fe0;
  --on-accent: #ffffff;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0d1117;
    --text: #e6edf3;
    --accent: #6d95ff;
    --on-accent: #0d1117;
  }
}

body { background: var(--bg); color: var(--text); }
.btn { background: var(--accent); color: var(--on-accent); border: none; padding: 8px 16px; border-radius: 6px; }

The accent is lighter in dark mode so that it stands out against the dark background, and the button text switches to dark to keep its contrast.

Key takeaway#

Finished this lesson?