Published at
Updated at
Reading time
3min
This post is part of my Today I learned series in which I share all my web development learnings.

I've thought that the new light-dark() CSS function can be used as a drop-in replacement for prefers-color-scheme media queries. Today I learned that they don't always behave the same.

Here's how MDN describes the new color function:

The light-dark() CSS <color> function enables setting two colors for a property [...] without needing to encase the theme colors within a prefers-color-scheme media feature query.

If you wonder about light-dark() browser support, here we go.

MDN Compat Data (source)
Browser support info for `light-dark()`
chromechrome_androidedgefirefoxfirefox_androidsafarisafari_iossamsunginternet_androidwebview_android
12312312312012017.517.527.0123

Now, I thought you could replace all your verbose color scheme queries with short light-dark() one-liners.

/* The old way of flipping dark mode styles */
.someElement {
  background: #eee;
  color: #333;
  border-color: #ddd;

  @media (prefers-color-scheme: dark) {
    background: #333;
    color: #eee;
    border-color: #444;
  }
}

/* The new way of flipping dark mode styles */
.someElement {
  background: light-dark(#eee, #333);
  color: light-dark(#333, #eee);
  border-color: light-dark(#ddd, #444);
}

Unfortunately, things aren't that easy.

light-dark() requires a color-scheme

The first thing to realize is that light-dark() requires a properly set color-scheme. Here's MDN again:

To enable support for the light-dark() color function, the color-scheme must have a value of light dark, usually set on the :root pseudo-class.

Without setting color-scheme: light dark; on :root or a surrounding container, the light-dark() function won't work. Flip your operating system's color theme and you'll see that light-dark() doesn't work below.

Without setting color-scheme: light dark;, light-dark() isn't picking up the current color scheme...

Component using light-dark()
Component using prefers-color-scheme

The prefers-color-scheme query, on the other hand, will react to your operating system's preferences no matter what. This different behavior seems a little strange, but I guess there are valid compatibility or legacy reasons for it. Adding color-scheme: light dark; is quickly done, right?

Everything works if color-scheme: light dark; is set!

Component using light-dark()
Component using prefers-color-scheme

While working on the new Web Weekly site, I implemented a color theme toggle and things got interesting.

Web Weekly redesign showing the light and dark mode in a split view

prefers-color-scheme doesn't consider the color-scheme

The Web Weekly redesign is Tailwind-based and to implement a light and dark mode I added custom color variables to my tailwind.config.ts to avoid placing dark:* classes everywhere.

export default {
  theme: {
    extend: {
      colors: {
        // light / dark styles
        "ld-slate-50":
          "light-dark(oklch(98.4% 0.003 247.858), oklch(12.9% 0.042 264.695))",
        "ld-slate-100":
          "light-dark(oklch(96.8% 0.007 247.896), oklch(20.8% 0.042 265.755))",
        "ld-slate-100/75":
          "light-dark(oklch(96.8% 0.007 247.896 / 0.75), oklch(20.8% 0.042 265.755 / 0.75))",
        // ...
      },
    },
  },
} satisfies Config;

This new color palette implements the standard Tailwind colors but uses light-dark() to automatically flip them. ld-slate-100 would be slate-100 in light mode and slate-800 in dark mode. Not gonna lie, I felt very smart when coming up with this idea.

The ld-* colors worked okay-ish until I had to fine-tune my dark mode styles and mix my automatic light-dark() color with dark:* colors using prefers-color-scheme.

My plan was to implement a dark/light mode toggle that changes the general color-scheme value and the rest should "just work", right? light dark would be the default, light should be set in light mode and dark in dark mode.

Turns out, this approach works great for light-dark() however prefers-color-scheme doesn't seem to care about color-scheme values and always relied on the OS settings.

Choose a Color Scheme
.container {
  color-scheme: light;
}
Component using light-dark()
Component using prefers-color-scheme
"prefers-color-scheme" doesn't consider the "color-scheme" property...

light-dark() always reflects the currently set color-scheme. For color-scheme: light dark; it falls back to the operating system settings, it returns the light variant for color-scheme: light; and the dark one for color-scheme: dark;. It does exactly what I expected.

prefers-color-scheme isn't affected by the color-scheme value and always falls back to the operating system's color scheme. Isn't this surprising?

I don't know the exact reasoning here, but it surely must be some backwards compatibility thing. If you know, reach out and let me know. I'd love to extend this post later.

So, whenever you read somewhere that light-dark() is just a shorter prefers-color-scheme query, be aware this isn't true! If you're dealing with color-scheme-based theming, this statement is fake news...

If you enjoyed this article...

Join 6.2k readers and learn something new every week with Web Weekly.

Web Weekly — Your friendly Web Dev newsletter
Reply to this post and share your thoughts via good old email.
Stefan standing in the park in front of a green background

About Stefan Judis

Frontend nerd with over ten years of experience, freelance dev, "Today I Learned" blogger, conference speaker, and Open Source maintainer.

Related Topics

Related Articles