This article was inspired by one of my continuing education students, who delivered a final project with the effect you see above. His version used JavaScript, but I realized the same result could be achieved in pure CSS.

This effect is best suited to a group of two dozen items or less. The goal is to filter the items by type, just as a true database would, only visually. In the example above, I’ve used travel photographs by Trey Radcliff.

The gallery images could be organized in almost any way you wish: I’ve contained the photographs in a figure element, but an unordered list or div could work equally well. Each image receives a class that identifies it as being a member of a particular set. As the class is only used as a CSS reference, and not as an actual style: an HTML5 data attribute could be equally effective.

The interesting part of the code lies immediately above the images. HTML5 allows form elements to be placed anywhere, so we can add a series of radio buttons with matching <label> tags immediately above the <img> elements. The for attribute on each <label> will turn the associated radio button with a matching id value on and off; as the buttons all share the same name value, activation of one button will turn the others off.

<input type="radio" name="zoom" id="france">
<label for="france">France</label>
<input type="radio" name="zoom" id="japan">
<label for="japan">Japan</label>
<input type="radio" name="zoom" id="us">
<label for="us">United States</label>
<figure id="zoom-gallery">
	<img src="central-park-in-fall.jpg" alt="Central Park In Fall" class="us">
	<img src="kyoto-bamboo-walk.jpg" alt="Kyoto bamboo walk" class="japan">
	<img src="kyoto-forest-walk.jpg" alt="Kyoto forest walk" class="japan">
	<img src="paris-reflected.jpg" alt="Paris reflected" class="france">
	<img src="paris-skyline.jpg" alt="Paris skyline" class="france">
	<img src="yosemite-clouds.jpg"" alt="Yosemite clouds" class="us">

Now for the CSS. We’ll start with a basic setup:

figure#zoom-gallery {
	margin: 0;
	padding: 0;
figure#zoom-gallery img {
	width: 40%;
	max-width: 300px;
	margin: 3%;
	transition: .7s all ease-in-out;
label {
	color: #fff;
	font-family: Futura, Arial, sans-serif;
	padding: 1em;

(I’ve removed vendor prefixes from the code to keep it clean.)

Starting with a variation of the :checked CSS I have talked about previously, we add a sibling selector to zoom images in a particular set. I’ll show one example, for the photographs taken in Japan:

input#japan:checked ~ figure img.japan { 
	transform: scale(1.1);

At the same time, we want to diminish any images in the gallery that are not in that particular set. Appropriately enough, we use the :not selector. Again, for the photographs from taken of Japan:

input#japan:checked ~ figure img:not(.japan) { 
	transform: scale(0.9);
	opacity: 0.6;

Optionally, you can hide the radio buttons by setting them to display: none, leveraging the of the label elements to allow the :checked declarations to operate regardless. If you add this, it makes sense to place a :hover effect on the labels to make it clear that they act as links:

input[type="radio"] {
	display: none;
label:hover {
	cursor: pointer;

While there are many possible improvements that could be made – I’ve added an animated box-shadow to the image elements – that’s essentially it. The only downside to this approach versus a JavaScript solution is that it does not scale, but that’s not an issue when the number of elements is small and unchanging.

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