Serve 'Dark Mode' Images Natively
Tags: html, css, webdev, darkmode
Published: 2019-10-02 00:16:39

_**Note:** This article is cross-posted from my blog: https://rhyslloyd.me/serve-dark-mode-images-natively/_

More and more websites, particularly technical blogs, are leveraging the _prefers-color-scheme_ media query to provide a dark or light theme based on the user's OS preferences. 

I think this is _brilliant_. Personally, I like to dark-mode **all the things**, so this works for me. That said, when it comes to images (particularly screenshots), I find a white image in a dark theme can be quite jarring. _[My eyes! The goggles do nothing!](https://media.giphy.com/media/VKVDU8pvi3w4w/giphy.gif)_

_What if we could present different images tailored for a user's dark (or light) preferences?_

<video muted playsinline controls loop autoplay>
  <source src="https://res.cloudinary.com/justrhysism/video/upload/vc_vp9,q_70/posts/dark-mode-images-switch-cap-meta.webm" type="video/webm">
  <source src="https://res.cloudinary.com/justrhysism/video/upload/vc_auto/posts/dark-mode-images-switch-cap-meta.mp4" type="video/mp4">
  Screen capture demonstrating toggling OS dark and light modes and an image changing in a browser when the mode switch occurs.
</video>

Well, it turns out we can! _Natively_. That's right! No JavaScript, no CSS wizardry, just good ol' plain ol' **native HTML**.

_"I've seen enough, [gimmie the code already](#tldr-sandbox-demo)!"_

## The `prefers-color-scheme` media feature

This media feature is relatively new to browsers ([landed in Firefox](https://hacks.mozilla.org/2019/05/firefox-67-dark-mode-css-webrender/) in May 2019) and seems to be spreading like a <abbr title="or maybe rather some type of beautiful dark butterfly 🦋">locust plague</abbr> as those of us on the dark side clamber for some of that dark-mode goodness. 

The team at [MDN describe](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) it more succinctly:

> _"The prefers-color-scheme CSS media feature is used to detect if the user has requested the system use a light or dark color theme."_

_A very basic demonstration of a `prefers-color-scheme` media query:_

```css
body {
    /* Default Light Scheme */
    color: black;
    background: hsl(0, 0%, 85%);
}

@media (prefers-color-scheme: dark) {
    body {
        /* Lovely moody Dark Scheme */
        color: white;
        background: hsl(0, 0%, 15%);
    }
}
```

## The `<picture>` element

_I suspect many of you may have just had the **aha!** moment I had a few weeks ago and figured it out. You might as well just stop reading right now and go and figure out how to extend it further_ 😉.  

The `<picture>` element provides a nice native mechanism for presenting different images based on specific media queries.

_A generic `<picture>` element example stolen verbatim [from MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture):_

```html
<picture>
    <source srcset="/media/examples/surfer-240-200.jpg"
            media="(min-width: 800px)">
    <img src="/media/examples/painted-hand-298-332.jpg" />
</picture>
```

## With your powers combined...

Combining these two very standard out-of-the-box native techniques, we can serve different images depending on a user's preferred colour scheme!

This is a bit like the first time you see sour cream and sweet chilli sauce served together with your potato wedges; at first the combination of these two simple ingredients seems a _bit weird_, but together it just works **so well**. _(Oh wait, [is that just us Aussies...?](https://www.reddit.com/r/australia/comments/2572fh/til_wedges_with_sour_cream_and_sweet_chilli_sauce/))_

```html
<picture>
    <source srcset="https://placehold.co/400x300/1D1E22/fff?text=Dark+mode" media="(prefers-color-scheme: dark)">
    <img src="https://placehold.co/400x300?text=Light+mode" /> 
</picture>
```

_And a real-life example using some [pleb's twitter profile](https://twitter.com/justrhysism); open up your OS theme toggle and switch modes to see the image change:_

<picture>
    <source srcset="https://res.cloudinary.com/justrhysism/image/upload/f_auto,q_auto,w_auto,dpr_auto/posts/twitter-dark-mode-justrhysism-small-cap.png" media="(prefers-color-scheme: dark)">
    <img src="https://res.cloudinary.com/justrhysism/image/upload/f_auto,q_auto,w_auto,dpr_auto/posts/twitter-light-mode-justrhysism-small-cap.png" alt="Screenshot of @justrhysism's twitter account" style="width: 100%; max-width: 700px; margin: 1rem auto;" loading="lazy" /> 
</picture>

## Inevitable caveat

Whilst playing with this technique, I quickly realised that it **falls down if you've implemented** a manual toggle to switch the scheme locally on your website, like the way [Andy Bell describes in great detail](https://hankchizljaw.com/wrote/create-a-user-controlled-dark-or-light-mode/). To see what I mean, toggle the theme on this site by clicking the sun (or moon) on the top-right of the viewport.

This is because the media query doesn't consider your local site settings (which are bespoke), nor is it possible to use CSS selectors inside a media query.

_That said_, I'm sure it wouldn't take much to sprinkle on a bit of magic JS dust to get it all happening. A post for another time.

## TL;DR Sandbox Demo

In the meantime, check out my sandbox, open your OS scheme settings and have a play around.

{% codesandbox dark-mode-images-l3j8o %}

## Wrapping up

Sometimes the coolest little techniques are right under our noses, and are just a matter of combining a few ingredients we might not have previously thought about going together.

By combining the `prefers-color-scheme` media feature and the `<picture>` element which _supports_ media queries, we're able to display images better suited to the user's colour-scheme preferences.

Reach out to me on twitter ([@justrhysism](https://twitter.com/justrhysism)) and let me know of the cool things you're doing with this technique, or other simple combinations with powerful results. _I'd love to see them!_

Until next time, _[hooroo](https://en.wiktionary.org/wiki/hooroo)!_