Photograph of a corridor in Vallbona de les monges Monastery, Catalonia, Spain by José Luis Mieza
Vallbona de les monges Monastery, Catalonia, Spain
Interior photograph of Woodward Avenue Presbyterian Church in Detroit Michigan by Rich Harris
Woodward Avenue Presbyterian Church, Detroit Michigan
Interior photograph of Woodward Avenue Presbyterian Church in Detroit Michigan by Rich Harris
Woodward Avenue Presbyterian Church, Detroit Michigan

The Simple CSS Gallery I demonstrated on this blog several years ago is an easy and popular means of creating an image portfolio without the complications of JavaScript or . There are just two issues with the technique: the gallery first appears without a large default “hero” image, and it takes the user hovering over a thumbnail to present one.

In response to a recent request in comments, I’ve written a tutorial showing how to create a similar gallery using :target to produce a default image with on-click interaction. As promised, today’s entry demonstrates another method. This technique is slightly more advanced, but has the benefit of not requiring a JavaScript fallback.

The Trick

The technique uses a variation of a “CSS hack” I’ve discussed in two previous articles, Window Toggle Events and A Visual Database Gallery. It exploits five features of HTML5:

  • input elements can appear anywhere on a page, and do not need to be associated with a form
  • a label with the correct attribute value will function as a surrogate control for the radio button it is paired with
  • a radio button does not have to be visible for this relationship to be valid, so long as the markup is valid
  • label elements can have images as content, rather than text
  • CSS can monitor the status of a radio button via the :checked pseudo-selector

To make all of this work, the HTML for the page must be correct and valid, so that’s what I’ll concentrate on first.

The Thumbnails

<label for="vallbona">
	<img src="thumbnails/vallbona-de-les-monges.jpg" alt="Vallbona de les Monges" >
<input type="radio" id="vallbona" name="churchy">
<label for="woodward">
	<img src="thumbnails/woodward-avenue-presbyterian.jpg" alt="Woodward Avenue Presbyterian ">
<input type="radio" id="woodward" name="churchy">
<label for="woodward2">
	<img src="thumbnails/woodward-avenue-presbyterian2.jpg" alt="Woodward Avenue Presbyterian">
<input type="radio" id="woodward2" name="churchy">

For ease of use, the thumbnail images are all relatively small and share the same aspect ratio.

There are just three rules to follow in the markup above:

  • The for attribute value in each label must match the id value of the associated radio input, just like an ordinary form
  • Each id value must be unique
  • All the radio button input elements must share the same name value so that they work to switch each other off.

It’s a good idea to add the thumbnail markup to your page first in order to check that the radio buttons (still visible at this stage) switch each other off as they are selected.

The Hero Images

The markup of the large images is even simpler. On your page, it immediately follows the code above:

<div id="churches">
	<img src="vallbona-de-les-monges.jpg" id="monges" alt>
	<img src="woodward-avenue-presbyterian.jpg" id="presby" alt>
	<img src="woodward-avenue-presbyterian2.jpg" id="presby2" alt>

I’ve left the alt values blank for these images to keep things simple for the purpose of this example: the only requirement is that each image must have a unique id. The images I’ve used are two photographs by Rick Harris of the Woodward Avenue Presbyterian Church, in Detroit, and the Vallbona de les Monges Monastery in Catalonia, Spain by José Luis Mieza.


The base CSS is also fairly simple:

input[type=radio] {
	display: none;
label img {
	width: 15%; display: block;
	float: left; clear: left;
	border-right: 40px solid #111;
label:hover {
	cursor: pointer;
div#churches {
	width: 70%;
	position: relative;
div#churches img {
	position: absolute;
	max-width: 100%;
	opacity: 0;
	transform: scale(0.8);
	transition: .5s all linear;

The declarations hide the radio buttons and set the images to be responsive, with the thumbnail images to the left. I’ve added a border-right to push the large images away from the thumbnails, but there are a number of other possible ways to achieve the same result. The large images are invisible by default, and 80% of their normal size. (Note that you’d have to add vendor prefixes for older versions of Firefox and other browsers).

Now to add the interactivity:

#vallbona:checked ~ #churches img#monges { 
	opacity: 1;
	transform: scale(1);
#woodward:checked ~ #churches img#presby {
	opacity: 1;
	transform: scale(1);
#woodward2:checked ~ #churches img#presby2 {
	opacity: 1;
	transform: scale(1);

The selectors here are a little more complicated: I’ve removed the optional element components from before most of the ids to keep things simpler. Translated into plain English, the declarations would read as “if a radio button with a particular id is selected by the user (either by clicking on the button or its associated label) then look inside an element that follows with an id of churches. If that element contains an image with a particular id, set that final element’s opacity to 1 and its scale to normal.”

While this approach forces a CSS declaration to be written for each thumbnail-hero pair, the fact that the applied styles are always the same means that you could make things more efficient with a group combinator:

#vallbona:checked ~ #churches img#monges, 
	#woodward:checked ~ #churches img#presby,
	 #woodward2:checked ~ #churches img#presby2 { 
		opacity: 1;
		transform: scale(1);

Wrapping Up

How do we present one of the hero images by default? By setting a checked value in the code of its associated radio button:

<input type="radio" id="woodward" name="churchy" checked>

That’s it. A large-scale image will always be presented by default, and the user can switch to viewing another if they wish by clicking on a thumbnail label. A further advantage of this system is that the page will survive a reset and revisit, as form options are remembered by the browser.

This could, of course, be taken a lot further: I’ve enhanced the code used in the demo above by adding slide-on image captions, based in part on the CSS presented in an earlier article.

Enjoy this piece? I invite you to follow me at to learn more.
Check out the CodePen demo for this article at