What’s prefers-color-scheme?
Here’s the thing — most of your users have already chosen light or dark mode in their system settings. They’re not starting fresh. They’ve made a preference, and respecting it creates a smoother experience from day one.
The
prefers-color-scheme
media query detects this choice automatically. It’s simple CSS, no JavaScript required. When someone visits your site, you can instantly apply their preferred theme without forcing them to toggle anything.
But here’s what we’ll cover — the syntax, how browsers handle it, and most importantly, how to combine it with a manual toggle so users aren’t locked into their system preference if they want something different on your specific site.
Quick Facts
- Browser support is solid — 95%+ of users globally
- Works on all modern browsers since 2019-2020
- Zero JavaScript needed for basic implementation
- Combines perfectly with manual theme toggles
The Media Query Syntax
The syntax is straightforward. You’ve got two options:
light
and
dark
. That’s it.
@media (prefers-color-scheme: dark) {
body {
background-color: #0a1628;
color: #f0f9ff;
}
}
@media (prefers-color-scheme: light) {
body {
background-color: #ffffff;
color: #0f172a;
}
}
You’ll write your dark theme styles inside the
@media (prefers-color-scheme: dark)
block and light theme styles in the
light
block. Simple as that.
Pro tip — structure your base styles as your default (usually light mode), then override with the dark media query. This ensures graceful degradation in older browsers that don’t recognize the media query.
Real Implementation
Let’s walk through an actual setup. You’ll want to organize your colour values — either with CSS custom properties or by grouping related styles together.
The most reliable approach is using CSS variables. Define them once, then reference them in your media queries. You’re not duplicating colour values everywhere, and changes become simple — update the variable, everything updates automatically.
:root {
--bg-primary: #ffffff;
--text-primary: #0f172a;
--text-secondary: #64748b;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #0a1628;
--text-primary: #f0f9ff;
--text-secondary: #bae6fd;
}
}
body {
background: var(--bg-primary);
color: var(--text-primary);
}
This approach means you write your HTML and component CSS once. The media query handles switching all the variables. No duplicate selectors, no bloated stylesheets.
Combining with Manual Toggles
Here’s where it gets interesting.
prefers-color-scheme
is perfect for respecting system preferences, but what if someone wants dark mode while their system is set to light? That’s where a toggle comes in.
You don’t actually need JavaScript to implement this — well, you do need it to persist the choice, but the theming itself can be pure CSS. Add a data attribute to your HTML element, then use that attribute in your selectors.
html[data-theme="dark"] {
--bg-primary: #0a1628;
--text-primary: #f0f9ff;
}
html[data-theme="light"] {
--bg-primary: #ffffff;
--text-primary: #0f172a;
}
@media (prefers-color-scheme: dark) {
html:not([data-theme]) {
--bg-primary: #0a1628;
--text-primary: #f0f9ff;
}
}
The
:not([data-theme])
selector is key here. When the user hasn’t manually chosen a theme, the media query applies. Once they click the toggle, the
data-theme
attribute takes over and overrides the system preference.
Browser Support & Fallbacks
The good news — support is genuinely solid now. Chrome, Firefox, Safari, Edge all support it. Even mobile browsers handle it properly. We’re talking 95%+ of global users.
The small percentage without support? They’ll get your base theme — whatever you define outside the media queries. This is why structuring with a default light theme first, then overriding with dark mode in the media query, works so well.
Chrome
76+ (2019)
Firefox
67+ (2019)
Safari
12.1+ (2019)
Edge
79+ (2020)
Best Practices
You’ve got the syntax. Now here’s how to actually implement this without creating maintenance headaches.
Test Both Modes
Don’t assume your dark mode looks good. Check it. Seriously. Open developer tools, simulate dark mode, and scroll through every page. Look for contrast issues, check that images still work, verify that all text is readable.
Use CSS Variables Consistently
Don’t mix hardcoded colours with variables. Pick one approach and stick with it. Variables are cleaner, easier to maintain, and prevent the mess of having colour values scattered everywhere.
Provide Manual Override
System preferences are a great default, but some users want control. A toggle button respects that choice. It’s not complicated — one data attribute and a few lines of JavaScript to save their preference.
Consider Images and Graphics
Some images might look odd in dark mode. White backgrounds show up too bright. You’ve got options — use CSS filters, provide separate image variants, or adjust opacity. Think through each image.
Wrapping It Up
prefers-color-scheme
isn’t a fancy feature — it’s a respectful one. Your users have already chosen their preference. You’re just acknowledging that choice and making their experience smoother from the moment they land on your site.
Start simple. Get the media query working. Use CSS variables for your colours. Test both modes thoroughly. Then, if you want, add a manual toggle so users can override if they need to.
That’s it. No massive JavaScript libraries, no complicated frameworks. Just CSS doing what it’s designed to do — adapt to the user’s environment.
Educational Information
This guide provides educational information about implementing dark mode detection and theme switching using CSS media queries. Implementation approaches, browser support details, and code examples reflect best practices as of April 2026. Your specific implementation may vary depending on your project requirements, existing codebase, and target audience. Always test thoroughly across different devices and browsers before deploying to production. Code examples are intended as learning references — adapt them to your particular design system and colour palette.