Filter Functionality with CSS3

Using the general sibling combinator and the :checked pseudo-class, we can toggle states of other elements by checking a checkbox or a radio button. In this tutorial we will be exploring those CSS3 properties by creating a experimental portfolio filter that will toggle the states of items of a specific type.

CSS3FilterFunctionality

Using the general sibling combinator and the :checked pseudo-class, we can toggle states of other elements by checking a checkbox or a radio button. In this tutorial we will be exploring those CSS3 properties by creating a experimental portfolio filter that will toggle the states of items of a specific type.

The idea is inspired by Roman Komarov’s brilliant “Filtering elements without JS” experiment where he uses checkboxes and radio buttons for filtering colored shapes.

The beautiful Dribbble shots used in the demos are by Mike from Creative Mints.

Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.

The Markup

Let’s start with the markup. Our aim is to have four filter buttons that, once clicked, will make the respective type appear or stand out. So, we’ll use some radio buttons that all have the same name since they should belong to the same group (hence only one can have the “checked” state). By default, we want the “all” radio button to be selected or checked. We’ll add some labels for the radio buttons that we will use in order to hide the radio buttons. Clicking a label will select the radio buttons with the respective ID:

<section class="ff-container">

	<input id="select-type-all" name="radio-set-1" type="radio" class="ff-selector-type-all" checked="checked" />
	<label for="select-type-all" class="ff-label-type-all">All</label>
	
	<input id="select-type-1" name="radio-set-1" type="radio" class="ff-selector-type-1" />
	<label for="select-type-1" class="ff-label-type-1">Web Design</label>
	
	<input id="select-type-2" name="radio-set-1" type="radio" class="ff-selector-type-2" />
	<label for="select-type-2" class="ff-label-type-2">Illustration</label>
	
	<input id="select-type-3" name="radio-set-1" type="radio" class="ff-selector-type-3" />
	<label for="select-type-3" class="ff-label-type-3">Icon Design</label>
	
	<div class="clr"></div>
	
	<ul class="ff-items">
		<li class="ff-item-type-1">
			<a href="http://dribbble.com/shots/366400-Chameleon">
				<span>Chameleon</span>
				<img src="images/1.jpg" />
			</a>
		</li>
		<li class="ff-item-type-2">
			<!-- ... -->
		</li>
		<li class="ff-item-type-3">
			<!-- ... -->
		</li>
		<!-- ... -->
	</ul>
	
</section>

The unordered list will contain all the portfolio items as anchors with an image and a span. Each list element will have a type class that will make it possible to identify those elements and “filter” them when we click on one of the radio buttons.

The CSS

We’ll be going through three example effects, but first, let’s take a look at the common style.

I will omit all the vendor prefixes, but you will, of course, find them in the files.

The main section container will have a specific width:

.ff-container{
	width: 564px;
	margin: 10px auto 30px auto;
}

The labels will cover the radio buttons and we’ll give them a fancy gradient and some subtle box shadows:

.ff-container label{
	font-family: 'BebasNeueRegular', 'Arial Narrow', Arial, sans-serif;
	width: 25%;
	height: 30px;
	cursor: pointer;
	color: #777;
	text-shadow: 1px 1px 1px rgba(255,255,255,0.8);
	line-height: 33px;
	font-size: 19px;
	background: linear-gradient(top, #ffffff 1%,#eaeaea 100%);
	float:left;
	box-shadow: 
		0px 0px 0px 1px #aaa, 
		1px 0px 0px 0px rgba(255,255,255,0.9) inset, 
		0px 1px 2px rgba(0,0,0,0.2);
}

At the corners we want some rounded edges, so the first label and the last one will get this specific border radius:

.ff-container label.ff-label-type-all{
	border-radius: 3px 0px 0px 3px;
}
.ff-container label.ff-label-type-3{
	border-radius: 0px 3px 3px 0px;
}

For every checked radio button, we want the respective label to have this “pressed” style:

.ff-container input.ff-selector-type-all:checked ~ label.ff-label-type-all,
.ff-container input.ff-selector-type-1:checked ~ label.ff-label-type-1,
.ff-container input.ff-selector-type-2:checked ~ label.ff-label-type-2,
.ff-container input.ff-selector-type-3:checked ~ label.ff-label-type-3{
	background: linear-gradient(top, #646d93 0%,#7c87ad 100%);
	color: #424d71;
	text-shadow: 0px 1px 1px rgba(255,255,255,0.3);
	box-shadow: 
		0px 0px 0px 1px #40496e, 
		0 1px 2px rgba(0,0,0,0.1) inset;
}

Since we have all our elements in one level, we use the general sibling combinator which is represented by a “tilde” (~) character, in order to reach the respective label. With this “trick” we will also get to all those different types in the portfolio list.

The inputs can be hidden, since we have our labels that will do the job:

.ff-container input{
	display: none;
}

Let’s move on to the item list:

.ff-items{
	position: relative;
	margin: 0px auto;
	padding-top: 20px;
}

The anchor and the span element will have the following style:

.ff-items a{
	display: block;
	position: relative;
	padding: 10px;
	background: #fff;
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
	margin: 4px;
	width: 160px;
	height: 120px;
}
.ff-items a span{
	display: block;
	background: rgba(113,123,161, 0.9);
	font-style: italic;
	color: #fff;
	font-weight: bold;
	padding: 20px;
	position: absolute;
	bottom: 10px;
	left: 10px;
	width: 120px;
	height: 0px;
	overflow: hidden;
	opacity: 0;
	text-align: center;
	text-shadow: 1px 1px 1px #303857;
	transition: all 0.3s ease-in-out;
}
.ff-items a:hover span{
	height: 80px;
	opacity: 1;
}
.ff-items li img{
	display: block;
}

When we hover over an anchor, we’ll make the span appear from the bottom, animating its opacity with a transition.

Allright, that was all the “common” style. Now, let’s see how we can filter those elements in style!

Example 1

CSS3FilterFunctionality01

In the first example, we’ll simply make the items that are selected (i.e. when the respective radio button is “checked”) have the highest opacity.
We’ll add a transition to the list item for the opacity:

.ff-items li{
	margin: 0px;
	float: left;
	opacity: 0;
	width: 188px;
	height: 148px;
	transition: opacity 0.6s ease-in-out;
}

Then we’ll use the general sibling combinator to target the selected type and set the opacity to one:

.ff-container input.ff-selector-type-all:checked ~ .ff-items li,
.ff-container input.ff-selector-type-1:checked ~ .ff-items .ff-item-type-1,
.ff-container input.ff-selector-type-2:checked ~ .ff-items .ff-item-type-2,
.ff-container input.ff-selector-type-3:checked ~ .ff-items .ff-item-type-3{
	opacity: 1;
}

Since we have the “all” type checked by default, all items will initially have the opacity 1.

Now, all the other items should get a very low opacity once we choose a type other than “all”. We’ll use the :not() selector to specify that the list elements that don’t have the selected type class should have an opacity of 0.1:

.ff-container input.ff-selector-type-1:checked ~ .ff-items li:not(.ff-item-type-1),
.ff-container input.ff-selector-type-2:checked ~ .ff-items li:not(.ff-item-type-2),
.ff-container input.ff-selector-type-3:checked ~ .ff-items li:not(.ff-item-type-3){
	opacity: 0.1;
}

The description span of those list elements should not be shown on hover:

.ff-container input.ff-selector-type-1:checked ~ .ff-items li:not(.ff-item-type-1) span,
.ff-container input.ff-selector-type-2:checked ~ .ff-items li:not(.ff-item-type-2) span,
.ff-container input.ff-selector-type-3:checked ~ .ff-items li:not(.ff-item-type-3) span{
	display: none;
}

And that was the first example. Let’s take a look at the next one.

Example 2

CSS3FilterFunctionality02

In this example, we want the selected items to scale up while the others scale down and become more transparent. So, let’s add a transition to the list elements:

.ff-items li{
	margin: 0px;
	float: left;
	width: 188px;
	height: 148px;
	transition: all 0.6s ease-in-out;
}

By default, we will have all the list items scaled normally and with full opacity. When checking one type, we want those respective item to scale up a bit and have full opacity, too:

.ff-container input.ff-selector-type-1:checked ~ .ff-items .ff-item-type-1,
.ff-container input.ff-selector-type-2:checked ~ .ff-items .ff-item-type-2,
.ff-container input.ff-selector-type-3:checked ~ .ff-items .ff-item-type-3{	
	opacity: 1;
	transform: scale(1.1);
}

Note here, that we did not include the “all” type like before.

The other items we’ll scale down and apply a low opacity level:

.ff-container input.ff-selector-type-1:checked ~ .ff-items li:not(.ff-item-type-1),
.ff-container input.ff-selector-type-2:checked ~ .ff-items li:not(.ff-item-type-2),
.ff-container input.ff-selector-type-3:checked ~ .ff-items li:not(.ff-item-type-3){
	opacity: 0.1;
	transform: scale(0.5);
}

And we’ll again hide the description span for the items that don’t have the selected type:

.ff-container input.ff-selector-type-1:checked ~ .ff-items li:not(.ff-item-type-1) span,
.ff-container input.ff-selector-type-2:checked ~ .ff-items li:not(.ff-item-type-2) span,
.ff-container input.ff-selector-type-3:checked ~ .ff-items li:not(.ff-item-type-3) span{
	display:none;
}

Example 3

CSS3FilterFunctionality03

The last example is just an experiment. We want to do something a bit more tricky here: upon selecting a type, we want to scale all items down and then scale only the items with the selected type back up again.

We basically want to make the other items disappear and since we cannot really animate the display property (not even with some combination of opacity), we use this little trick: when we scale the items down, we’ll also animate the width of the ones that should disappear to 0.

So, let’s give the list items 0 width initially and scale them down to 0 (careful, you will want to avoid this practice for older browsers):

.ff-items li{
	margin: 0px;
	float: left;
	height: 148px;
	width: 0px;
	transform: scale(0,0);
}

When the “all type” get’s checked, we will want the scale to be 1 and the width 188px:

.ff-container input.ff-selector-type-all:checked ~ .ff-items li{
	width: 188px;
	transform: scale(1,1);
	transition: transform 0.3s linear;
}

Remember, we are in this state initially, since we have the “all types” check by default, but we will also transition into the state, if we are currently viewing another type and go back to the “all types”.

Now, when we check one specific type, the items with that type class will first transition to their initial state, first scale down and with a delay of 0.3 seconds, get a width of 0 again.

At the same time, the items that are not of the selected type (those ones that we get with the :not() selector) will have the “scaleDown” animation running, which will basically do the same thing: scale them down and give them a width of 0.

After 0.4 seconds, well make the selected type appear again, with the “scaleUp” animation:

.ff-container input.ff-selector-type-1:checked ~ .ff-items .ff-item-type-1,
.ff-container input.ff-selector-type-2:checked ~ .ff-items .ff-item-type-2,
.ff-container input.ff-selector-type-3:checked ~ .ff-items .ff-item-type-3
{
	transition: transform 0.3s linear, width 0s linear 0.3s;
	animation: scaleUp 0.3s linear 0.4s forwards;
}
.ff-container input.ff-selector-type-1:checked ~ .ff-items li:not(.ff-item-type-1),
.ff-container input.ff-selector-type-2:checked ~ .ff-items li:not(.ff-item-type-2),
.ff-container input.ff-selector-type-3:checked ~ .ff-items li:not(.ff-item-type-3)
{
	animation: scaleDown 0.3s linear forwards;
}
@keyframes scaleUp {
	50% { width: 188px; transform: scale(0,0); }
    100% { width: 188px; transform: scale(1,1); }
}
@keyframes scaleDown {
	0% { width: 188px; transform: scale(1,1);}
	99% { width: 188px; transform: scale(0,0);}
    100% { width: 0px; transform: scale(0,0); }
}

Note that this example is very experimental and it will only work properly in browsers supporting CSS animations. In Firefox 9.0.1 the behavior is not as expected (hovering over the labels seems to falsely trigger something) but it works in Aurora 11.0a2, so it might be some kind of bug.

Demos

  1. Demo 1: Opacity
  2. Demo 2: Scale
  3. Demo 3: Scale and “remove”

And that’s it! I hope you enjoyed this tutorial and find it inspiring!

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay in the loop: Get your dose of frontend twice a week

👾 Hey! Looking for the latest in frontend? Twice a week, we'll deliver the freshest frontend news, website inspo, cool code demos, videos and UI animations right to your inbox.

Zero fluff, all quality, to make your Mondays and Thursdays more creative!

Feedback 66

Comments are closed.
  1. Great tutorial, I’ve learned a ton from it. However for the 3rd example I needed my time get everything figured out. That was a tough one.

  2. Awesome!! THE best css3 functionality I’ve seen so far! simple awesome! Great job Mary Lou! 🙂

  3. Anyone out there who could recommend such a nice pease of code with working cross plattform (IE, Chrome, FF, aso) ??

    • Hey Harry,

      as far as I tried there isn’t really a problem in any of the major browser with it. The only thing which doesn’t work is the delay added when switching between categories. The filter function however works great for me in IE, Chrome and FireFox.

      Here my interpretation of this tutorial: http://www.messenbrink.eu/photographyCSS3.php

  4. Very smart approach to the radio buttons/labels hack.

    Just consider that the label[for] does not work on IOS, so it requires a soecific javascript fix to select the input when the labels are clicked.

    Kudos!