Skip to content
Development

light-dark() now takes images, not just colours

The light-dark() function takes two values and returns the first when the element is in light mode and the second when it is in dark mode. When it first shipped it took colours only. Now it takes images too, which makes swapping illustrations with the colour scheme much simpler.

The whole trick

:root {
  color-scheme: light dark;
}

.hero {
  background-image: url(day.svg);
  background-image: light-dark(url(day.svg), url(night.svg));
}

Three things to note:

  • color-scheme: light dark is required. light-dark() does not read the operating system by itself. It follows the color-scheme applied to the element, and this line lets the page follow the system setting.
  • The first background-image line is the fallback. A browser that doesn't understand light-dark() with images skips the second line and keeps the day image.
  • No more @media (prefers-color-scheme: dark) block repeating the selector. The light and dark values sit side by side on one line.

Example 1: Day and night backgrounds

The box below uses exactly the two background-image lines above, with two inlined SVG images. Press a button to change the color-scheme of the box alone, or pick “Follow page” to let the box follow your device’s light or dark mode. In Chrome, transition: background-image also gives a cross-fade between the two images.

Example 1. One box, two images, light-dark() picks by color-scheme.

The main CSS:

:root {
  color-scheme: light dark;
}

.scene {
  background-size: cover;
  background-position: center;
  background-image: url(day.svg);                            /* older browsers stop here */
  background-image: light-dark(url(day.svg), url(night.svg)); /* newer browsers pick by color-scheme */
  color: light-dark(#3b2416, #f3e6d3);
  transition: background-image 600ms ease, color 600ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .scene { transition: none; }
}

The buttons only set element.style.colorScheme on the box. Choosing the image is still light-dark()’s job.

Example 2: Not just backgrounds

light-dark() works from the color-scheme of each element, so you can force an area to stay light or dark with color-scheme on that element. The function also works in other properties that take images: list-style-image for bullets and content for an icon before a heading.

Example 2. Three boxes with three different color-schemes.
.panel {
  background: light-dark(#fffaf3, #1d1b22);
  color: light-dark(#3b2416, #ece2d6);
  border: 1px solid light-dark(#ecdcc8, #34303d);
}

/* Illustration: background-image */
.panel .art {
  background: center / cover no-repeat;
  background-image: light-dark(url(day.svg), url(night.svg));
}

/* Bullets: list-style-image */
.panel ul {
  list-style-image: light-dark(url(sun.svg), url(moon.svg));
}

/* Icon before the heading: content */
.panel h3::before {
  content: light-dark(url(sun.svg), url(moon.svg));
  margin-right: .4rem;
}

Three boxes share that CSS and give three different results:

<div class="panel" style="color-scheme: light">…</div> <!-- always light: day image, sun bullets -->
<div class="panel" style="color-scheme: dark">…</div>  <!-- always dark: night image, moon bullets -->
<div class="panel">…</div>                             <!-- follows the page, inherited from :root -->

Example 3: Does your browser support it?

Use CSS.supports() to check directly. The box below runs the check in your own browser.

Example 3. CSS.supports() results in your own browser.

To run it yourself, paste this into the console:

const img = 'light-dark(url(a.svg), url(b.svg))';
for (const prop of ['background-image', 'list-style-image', 'mask-image', 'content']) {
  console.log(prop, CSS.supports(prop, img) ? 'supported' : 'not supported');
}
console.log('color (colours only, already shipped)', CSS.supports('color', 'light-dark(white, black)'));

In CSS you can do the same with @supports (background-image: light-dark(url(a.svg), url(b.svg))).

Things to watch

  • If your design system uses an attribute such as data-theme, make sure it also sets color-scheme. The demos above use daisyUI, which already does, so they follow its light/dark toggle with no extra line.
  • Give both images the same size and layout so the switch doesn't jump.
  • An SVG can carry its own animation and respect prefers-reduced-motion inside the file, like the sun and stars in example 1.

Browser support. light-dark() taking images is new. Example 3 tells you exactly how far your current browser goes. Where it isn't supported yet, keep the fallback declaration first, as in the code above.