Ben Fiedler

CSS: prefers-color-scheme

When developing tools that interface with users in a visual sense, I believe it is important to cater to the users’ wants (and needs) as much as possible, whilst retaining a recognizable product identity. Probably the brightest decision in this regard is the background color: light or dark?

Specifically regarding webpages, a variety of solutions have been developed: JavaScript triggers, cookie-based theming, user stylesheets and time-based theming. All of these solution falls short in one important aspect: they do not cover 100% of a user’s interaction with a site. The first two solutions have no safe default - they may always show the user the “wrong” theme first, and require active engagement to fix, which is a burden on the user. User stylesheets do work on first visit, however one often has to deal with broken sites, since they were not designed for theming. Finally, time-based theming is silly in its own right1.

The solution

In 2017, the CSS Media Queries Level 5 specification was released, and it proposes the best solution to date: a media query named prefers-color-scheme. It is set by your browser, and can be set to either light or dark - more values may be supported in the future. Instead of requiring a user to interact with each site they visit, the decision is only made once at the browser, and can be applied to every site, even on the first visit! And it is supported by all major desktop and mobile browsers, simply marvelous.

Using this feature is as simple as adding a media query to your styling. Using CSS custom properties (“variables”), all you need to do is define the appropriate colors and you’re good to go.

@media (prefers-color-scheme: light) {
    // light styling
    // the default if no preference set

@media (prefers-color-scheme: dark) {
    // dark styling

The prefers-color-scheme still has one drawback: correct page rendering must be delayed until CSS is downloaded and parsed, and might cause flickering on slow connections, when the browser defaults to a white background but the user prefers dark mode for example. Using the color-scheme meta tag, the webpage can immediately signal which themes it supports, and the browser can react by styling the background dark immediately, before applying CSS, which prevents the aforementioned flickering. The first named theme is the author’s preferred default, which the browser can respect if the user has not given an explicit preference.

<!-- supports light and dark mode, defaults to light mode-->
<meta name="color-scheme" content="light dark">

Trying it out

This website fully supports theme selection based prefers-color-scheme, try it out! It even inverts images2 to perfectly match your preferred style, a feature I’m very proud of, even if it is virtually invisible (since almost nobody looks at both styles). See the website’s source to see the actual SCSS source files.

On desktop browsers you can test it using the developer tools (Firefox, Chrome, Safari). On mobile, modern smartphones (both iOS and Android) set your preferred color scheme based on the system theme, so changing that should also change the appearance of this site.

Sadly, many of the sites I interact with daily do not support prefers-color-scheme. Especially people who rarely do frontend work (such as myself) may not know of this feature, and I hope to raise awareness for user-friendly theme support.

If you have a personal blog, product or company website or any other side, consider adding support for prefers-color-scheme to your theme. Even though it has no impact on the majority of people, we ultimately want design our products to be as user-friendly as possible. Right?

  1. Bonus question: server time or client time? One is inaccurate for large parts of your visitors, the other one requires JavaScript. ↩︎

  2. when it makes sense, so photos for example are left as-is ↩︎

Articles from blogs I follow

Structured Logging with slog

The Go 1.21 standard library includes a new structured logging package, log/slog.

via The Go Blog on

Status update, August 2023

Hi! Let me start this status update with an announcement: from 2023-08-28 to 2023-10-01 (!), I will be on leave, so I will have reduced availability. Don’t be surprised if I miss some e-mails, and feel free to ping me again (more generally, please do ping me …

via emersion on

I had a great time at DEF CON 31

I've always admired DEF CON from a distance. I've watched DEF CON talks for years, but I've never been able to go. This year I was able to go, and I had a great time. This post is gonna be about my experiences there and what I learned. In short: I…

via Xe's Blog on