Pure CSS image lightboxes

I don’t like using JavaScript unnecessarily. That’s why I love pure CSS solutions. As browsers are getting better and better at implementing CSS3 features, pure CSS solutions are getting slicker and more production-ready.

One of the few pure CSS solutions I built recently was a pure CSS lightbox. I built my solution first and then looked at the tens of other solutions out there to compare and check if I missed anything important.

To check within CSS if a user has clicked on something can be achieved in various ways. You need to use (and sometimes abuse) either links and their link states (:hover, :active or :focus) or checkboxes/radio buttons and their on/off states (:checked) or anchor links and the state of their targets when the anchor in the URL is matching one (:target).
In the case of lightboxes, all techniques work by first hiding the element with the big image and then showing it depending on one of those states with pseudo-classes.

Using :target

Most of the existing solutions use :target. Their markup looks generally like this:

<a href="#id"><img src="path_to_thumbnail" /></a>
<a href="#" id="id"><img src="path_to_big_image" /></a>

It works by showing the big image only when it (or its parent) was targeted by using e.g. :target { display: block; }.

The main drawback is that it’s not very flexible because you need to add a different ID per image. I would also argue that it’s not very semantic as linking to something that is right underneath it would normally not need any link. It can be made keyboard-accessible, although the example is not as it’s closing by linking to “#”. It should rather link back to where it was called from.

Using :checked

The second most used technique is using :checked. Markup of solutions using that generally looks like this:

<label for="id"><img src="path_to_thumbnail" /></label>
<input type="checkbox" id="id" />
<label for="id"><img src="path_to_big_image" /></label>

It works by showing the big image when the checkbox is checked with e.g. :checked + label { display: block; }. Even when the checkbox itself is hidden, it will still be checked or unchecked when clicking its label. There are two labels because one is for checking and the other for unchecking (although they are actually the same label triggering the same thing, it works because one is shown every nth time you clicked it and the other every (n+1)th time).

Their main drawback is the same as with the :target technique, the need for different IDs per image makes it less flexible. I’m also unsure if this technique can be called semantic or not. On the one hand checkboxes are meant to toggle something on or off but on the other hand form elements in general are meant to provide data to be submitted to somewhere. This can be keyboard-accessible, although as it’s not clear that a checkbox is used, the user most probably won’t know to use the space key and not the enter key to open the big image.

Using :hover

Using :hover is out of the question nowadays as it wouldn’t reliably work on mobile devices, and it wouldn’t be keyboard-accessible either.

Using :focus

I used :focus (and :active) for my solution. I only found two other solutions using the same. They generally look something like this:

<a href="#"><img src="path_to thumbnail" /></a>
<div><img src="path_to_big_image" /></div>

You would only show the big image when the link around the thumbnail is focussed with e.g. :focus + div { display: block; }.

This is the least semantic and accessible as the main action only links to a nonsensical “#”. That’s why I don’t use links at all in my solution but add a tabindex="0" to the main element instead. But at least it is more flexible than the other solutions.

My solution(s)

My solution is flexible as it doesn’t need any specific IDs, it is fully keyboard-accessible, it uses nice animations and it lazy-loads the big images (i.e. it only loads them when they are requested). Most of the other solutions load the big images on page load which defeats one of the main points about a lightbox. (Although that fault is never due to the basic technique used as all of them could be changed to not do that.)

One of the main problems I encountered was that I couldn’t animate the opening of the big image while also lazy-loading it. You can only load images on request if you’re a) using background images and b) hide the element via display: none; (and not via any of the other techniques). But display is not animatable. So, I had to choose between lazy-loading and animating. I managed to find a solution in between where at least the opening is animated but not the closing. And because I couldn’t decide, I listed all three solutions (and a fourth ideal world but not working solution).

The main drawbacks (that this solution shares with all the other non-JavaScript solutions) are that the interaction with the lightboxes might not work the way users are used to. The Esc key does not close the big image (but clicking anywhere or tabbing away does) and navigating to or away from images does not work with right and left arrows but with Tab and Shift+Tab.
This particular solution might also confuse screenreaders because the tabindex neither explains what it does nor seems to be doing anything they could perceive. Does anyone know a solution for that? I thought of using an aria-label but if you think about it, that could only really say nonsensical things (like “activating this will do something that won’t affect you” or “ignore this”) unless you use the lightbox for something other than images.

Browser support is pretty good, it works in all modern browsers and even IE9 and Opera Mini.

You can see the code in action in a Fiddle below.

Update 14 May 2016: I deleted the Pen from CodePen (due to certain issues) and only use the Fiddle from JSFiddle instead.

Tags: , , , ,


One Response to “Pure CSS image lightboxes”

  1. Big Louis says:

    I think the :target solution is usually the most semantic one, and has the advantage of being navigable through the browser history…

    See for example https://madmurphy.github.io/takefive.css/

Leave a Reply