Dimming Images for 'Dark Mode'
Tags: webdev, darkmode, javascript, css
Published: 2020-01-14 10:02:03

_Note: This article is cross-posted from my blog: https://rhyslloyd.me/dimming-images-for-dark-mode/_

When applying a "Dark Mode" theme to your website or blog, it can be challenging to determine what to do with bright images to avoid blinding your users as they navigate your site; especially when there's no logical dark alternative.

In my last post, I demonstrated a straightforward way to [serve dark-mode alternative images](/serve-dark-mode-images-natively/). However, not all images have a logical dark alternative, nor does everyone have access to, skills with, or time for expensive software to [convert light images to dark](https://design.tutsplus.com/tutorials/how-to-turn-day-into-night-in-photoshop--cms-29530).

When I posted my [previous article on Reddit](https://www.reddit.com/r/webdev/comments/d6ch4g/adding_a_dark_mode_scheme_dont_forget_about_your/), many Redditors suggested using CSS filters to darken images. While I do believe that the approach is valid, I do think it is a slightly-different use-case than serving completely different images. That said, I think applying CSS filters to darken images could work; so long as the user can _remove_ the filters. "Opt-in brightness" if you will.

I wanted to explore this idea further, and try to find some approaches to apply this technique of "dimming" an image and the user toggling the "dimming" on and off.

## CSS Filters for Image Dimming

There are many ways to dim images using CSS filters, the example I've provided is just one way to achieve the effect. Any specific implementation should be adjusted to match the look and feel of the site.

> If you're new to CSS filters, I recommend taking a look at [this article on Alligator.io](https://alligator.io/css/css-filter-examples/) which demonstrates several filters with examples of what they do.

For this example, I've chosen to reduce the image brightness and remove some colour to soften the brightness and vividness slightly.

```css
.dim {
	filter: brightness(40%) grayscale(50%);
	transition-property: filter;

	/* Transition to "off" (dim) */
	transition-duration: 0.5s;
	transition-delay: 0.5s; /* Delay slightly */
	transition-timing-function: ease-out;
}

.dim:focus,
.dim:active {
	filter: brightness(100%) grayscale(0%);

	/* Transition to "on" (bright) */
	transition-duration: 0.1s;
	transition-delay: 0.01s; /* No delay. Set to 0.01 Because weird things happen when set to '0' */
	transition-timing-function: ease;
}
```

## Approach #1: CSS only

Where possible, I prefer to avoid javascript (a conversation for another time); so when looking for solutions I tend to search for browser-native or CSS-only solutions first.

My first approach was to reveal the image when it is clicked and the [`:active` pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:active) is triggered.

Since, `:active` is only erm... _active_ on **mouse down**, once the user releases the mouse button the image will return to it's dimmed state. The effect can be softened slightly with a slight transition delay. Adding the [`:focus` pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus) also makes this approach _somewhat_ more accessible; however, it could be a little surprising as there's no indication that the image will brighten when it receives focus.

{% codepen https://codepen.io/justrhysism/pen/eYOQLvz %}

The beauty of this approach is in its simplicity. It doesn't require any extra markup and uses just a single of pseudo-class for the effect. However, this approach completely breaks down on touch-devices, and either doesn't work or is intermittent on iOS devices (in particular) resulting in a rather _infuriating_ experience.

## Approach #2: HTML+CSS

Because the first approach doesn't effectively work on touch devices, I wanted to find a way _I could_ get it to work (without JavaScript). To achieve this we can leverage the _old-school_ "[checkbox hack](https://css-tricks.com/the-checkbox-hack/)".

The "hack" here is exploiting CSS's [:checked pseudo-class selector](https://developer.mozilla.org/en-US/docs/Web/CSS/:checked) and the [general sibling combinator](https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_combinator) in the form of:

```css
input:checked ~ img {
	filter: brightness(100%) grayscale(0%);
}
```

...which enables us to apply different styling when the checkbox is _checked_.

Wrapping the image with a `<label>` and placing a hidden checkbox within it (creating an [implicit association](https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/How_to_structure_an_HTML_form#The_%3Clabel%3E_element)), we can provide a simple (_somewhat_ accessible, assuming it's labelled correctly) toggle which _actually works_ on touch devices.

Since we're already adding in some extra markup, let's add in a cute SVG icon to indicate to the user that the image has been dimmed deliberately and _not a bug in our CSS_ which we were unable to achieve with the [CSS-only approach](#approach-1-css-only).

{% codepen https://codepen.io/justrhysism/pen/JjPeaWy %}

While I do love HTML and CSS-only solutions, the most apparent issue with this approach is all the **extra markup required** to achieve the effect. Frankly, _it would be pretty damn annoying_ to manually wrap images you wish to dim with all the additional markup, particularly if you're trying apply the technique to an existing site (say, for example, [a blog](https://rhyslloyd.me)). It would be a rather arduous task to go back through every page and/or post to apply the technique.

## Approach #3: JavaScript

Recognising the impracticality of the manual HTML+CSS only approach, let's sprinkle _a little_ JavaScript which enables us to apply the technique.

> _For brevity, I'll write another post (soon, I promise!) breaking down the how and why of what the JavaScript is doing._

Building on [approach #2](#approach-2-htmlcss), we can apply a very similar technique. Since we're already writing JavaScript, it's (arguably) simpler for us to add the relevant click and keyboard listeners to toggle the dimming effect rather than using the "checkbox hack".

{% codepen https://codepen.io/justrhysism/pen/gOYqBPq %}

The essential advantage of the JavaScript approach is the ability to apply the technique to existing pages without necessarily needing to go back and update the markup; which means it can be applied to a website retrospectively.

## Wrapping Up

With all of that said, it may not be desirable to apply the dimming technique to _all_ images. In my next post, I'll explore a method for automatically determining image brightness so only "bright" images are dimmed.

In the meantime, reach out to me on Twitter ([@justrhysism](https://twitter.com/justrhysism)) and let me know if you've explored this idea already, and any alternative approaches you might have!

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