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.
    • For me too. Actually i am working right now on it, if someone could bring me a light, i ll appreciate a lot. The click area doens’t work on Ipad, i tested checkbox and radio.

  1. Not working in Safari 5, and Firefox 8 has strange symptoms. Apart from that an awesome bit of code.

  2. Love this, very very, very nice. In particular like demo 1. Will be perfect for any portfolio site.

    Great work

  3. It would be great to extend this tutorial, and make an animation for filtering like the jquery quicksand plugin !
    Please take it into consideration πŸ™‚

  4. Mary Lou You are just amazing!! This tutorial is very helpful and beautifully done. thank you so much for your work.

  5. Hello Mary Lou,

    In Firefox there is a bit of a bug when you hover on the menu at the top, on the third demo ONLY. The rest, works like a charm.

    Great stuff and great work.

  6. Thankyou, amazing in latest browsers!

    Do you have a list of compatible browsers for this code?

    What workaround would you suggest to mimic the same effect as demo1 in older browsers that don’t support these css3 techniques?

  7. Mary Lou, thanks for sharing this tutorial is very helpful, I am looking forward to apply this

  8. For some reason all 3 demos looked exactly the same to me. Very neat stuff but they looked exactly the same.

    Am I missing something?

    Btw, CSS3 is very powerful!

  9. like itoctopus i see 3 tims the same and only the hover effect. Windows XP; FF9, chrome 16, safari 5.
    ps like your work a lot. cheers!

  10. Useless! Not working in IE8/FF8/Safari Mobile. Far better is to use good old jQuery, which works even in IE7!

  11. Hi, thanks again for all your feedback! As I mentioned, in Firefox 9.0.1 the behavior is buggy…

    @badi, maybe it’s because of Win XP? Sorry, I can’t test it…

    @Ian, let’s hope it will be useful in the near future πŸ™‚ …until then, you can always use a JavaScript fallback for the browsers that don’t support those properties. It’s kind of harsh to call it useless, don’t you think?

    Cheers, ML

  12. lol…man….you are soooo…creative! This is a great alternative to quicksand and isotope, without the scripting. You never cease to amaze me! Honestly, I wish I could have you do some brainstorming on my project one of these days πŸ™‚

    -Sin

  13. The timing for this is absolutely perfect. Now it’s all about implementing it. Happy Days πŸ˜€

  14. Awesome, I’ve been experimenting too, glad to see there are still coders out there not replying solely on plug-ins to do cool transitions/animations. Keep up the good work πŸ™‚

  15. Hello Mary Lou, i have been following all ur articles since very long…. All of them just blow my mind. You have really made the flash system sound remote. Lol! Soon I would be using some of ur inventions in my website! http://www.klimpse.com

  16. Nice articles – keep up the good work, will be following you on Twitter! Some inspiring stuff there I will be using in future projects. Nice design on Tympanus too, clean and tidy, tidy and clean. Cheers!

  17. Nice work Mary Lou.
    I suggest you to add a β€œpointer-events:none;” on disabled items.

  18. very nice and very useful.. i will use this in my upcoming project πŸ™‚ are we allowed to do that?
    TYI

  19. This is a great functionality!
    But i’ve a question: I can’t get it to work when I put a div around the input and label fields.
    Can someone tell me which selector I should use in my css?
    Thanks in advance!

  20. Hi,
    Love this method, but i can’t get it working with jquery uniform plugin which I use. Any suggestion?

  21. When you want to use multiple selectors:
    change the input type to ‘checkbox’ instead of ‘radio’ and give multiple ff-item-type to the li-tags

    its a great tut btw ! love the result, exactly what I needed