Dimming Images for 'Dark Mode'

Cover image

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. 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.

When I posted my previous article on Reddit, 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 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.

.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 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 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.

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".

The "hack" here is exploiting CSS's :checked pseudo-class selector and the general sibling combinator in the form of:

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), 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.

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). 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, 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".

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) and let me know if you've explored this idea already, and any alternative approaches you might have!

Until next time, hooroo!