Build a Responsive, Filterable Portfolio, with CSS3 Twists


The inherent visual appeal of filterable portfolios (like the Tuts+ hub) has made them very popular. Today, we’ll be making one using straight-forward markup, CSS3 and a little bit of jQuery.

Step 1: The File Structure

We’ll be using the following file structure for our project:

File Structure

Pull a project together based on these files (you'll need to grab HTML5 Shiv) and let’s get started with the HTML markup in index.html.

Step 2: HTML Head

Let's keep tempo high and rattle off a list of things we need to do to create the <head>:

  • Declare the media type and character set of the page.
  • Set our viewport’s width to be the same as the device’s width and set the initial zoom to 1 (Read more about this here)
  • Give our page a title.
  • Attach a favicon (interested in retina-proof favicons?)
  • Attach our main style sheet (style.css)
  • Attach our style sheet for handling media queries (media-queries.css)
  • Link to the latest version of jQuery.
  • Add an HTML5 Shiv for handling HTML5 compatibility issues with old browsers.

And here's what we get:

Step 3: HTML Basic Markup

In the body, we first add a ‘container’ to hold all our elements within a set width, centered on the page. Within that we add (get ready for another rapid fire list):

  • A <header> for our heading (‘John Doe’).
  • A basic navigation (<nav>) comprising a <ul> menu, with five items, each with its respective ID.
  • A <section> for the thumbnails with the class ‘work’.
  • A <footer> with all the copyright stuff.

Step 4: HTML Figure and Image

We’ll be using the <figure> tag for our thumbnails and will apply the class of the respective category which it belongs to. Within the figure, we’ll add an <a> tag comprising the image (<img>) for the background of the thumbnail and a description list (<dl>) for the caption.

Step 5: HTML Caption (DL, DT, DD)

As mentioned above, we’ll be using a description list for our caption. Our description terms (<dt>) will be our small headings — Client and Role, for our descriptions (<dd>) — Envato and Illustration, respectively.

Step 6: The Complete HTML

Here’s what our completed HTML markup looks like:

Let's move on to the styling now.

Step 7: CSS Selection and Scrollbar

We’ll start by dealing with some playful elements; the selection state and the scrollbar (which are entirely optional) plus we'll haul in some fonts.

In the above code, we imported two webfonts from Google — Open Sans and PT Sans Narrow. Then, we simply set a dark gray background and a white text color for user selections.

We then set a width of 9px for our scrollbar (in Webkit browsers) and gave the ‘track’ a light gray background, a thin border and a mild inset box-shadow. Then, we gave a dark gray background to the scrollbar thumb and added a thin border to it.

Note: For more information on webkit-scrollbars, see Chris Coyier's post.

Step 8: CSS Body

We’ll give our body a light gray noise background and apply the ‘Open Sans’ font we imported earlier. We’ll also add a red top border for an enhanced finesse.

Make some noise...

Step 9: CSS Container

Now, for our container, we’ll set a width of 960px, a 10px top and bottom margin, and center it on the page by setting the right and left margins to ‘auto’. We’ll also force hardware acceleration on (some) mobile devices by using ‘-webkit-transform: translateZ(0);’.

Step 10: CSS Header

We’ll simply increase our heading font-size, center the text and give it a font-weight of 300 for a sleeker look.

Step 11: CSS Footer

We’ll center align the text horizontally, add the top and bottom margins of 50px each, set the text color to gray, and position it below the ‘work’ section by using ‘clear: both’.

Let's work on the navigation now.

Step 12: CSS Navigation

We’ll start by removing all the default styling from our <ul>. Then, we’ll add a 50px top and bottom margin and align the text to the center.

Now, by using ‘display: inline’, we’ll get all our <li>s to display in one line. We’ll set the cursor to ‘pointer’ and add a 10px right margin to each <li>. The default text color will be a light shade of gray which will turn black on hover.

We’ll also add a small transition time to animate the color changes.

Since the last <li> is, umm, the last-child, it doesn’t need any right margin. So, we’ll remove it.

Now, let’s add the slashes after the <li>s. We’ll accomplish this by using the ‘:after’ pseudo-selector. By setting its ‘content’ to ‘/’, color to light gray, and an appropriate left margin, we can produce a simple-yet-effective system of link separation. We’ll also ensure that the slashes don’t change color on hover by forcing their default color on li:hover too.

Again, we’ll have to remove the slash from the last <li>.

That’s all for the navigation, let’s get to the thumbnails now.

Step 13: CSS Thumbnails

First, we’ll add a 20px top and bottom margin to the ‘.work’ section.

Next, we’ll style each ‘.work figure’ (i.e. all our thumbnails). We’ll use ‘float: left’ to get them lined up. We’ll then add a 20px margin, set a height and width of 200px, and add a mild sepia effect by using ‘-webkit-filter: sepia(0.4)’. Then, we’ll set the position to relative so that we can absolute position other elements (the caption in this case) within the figure. We’ll then add a mild box-shadow which will also work as our border. We’ll also add a small transition time for our boxes to grow and scale down.

We’ll ensure that our image always fits the thumbnail by setting its height and width to 100%.

That’s all for our basic thumbnails. Let’s work on their captions now.

Step 14: CSS Captions

Description List

As we don’t want our caption to be visible initially, we’ll set its opacity to 0. Then, we’ll absolute position it (within the figure) and make it fill the figure fully by setting all the 4 properties — top, right, bottom, and left — to 0.

We’ll then set its line-height to 2.5 and give it a dark, translucent background. Since we’re using a dark background, we’ll set its text color to white. We’ll also add a small transition time to animate its opacity on figure:hover.

As we want it to appear on hovering on the thumbnail, we’ll set its opacity to 1 on figure:hover.

Description Terms

First, we’ll set their font-family to ‘PT Sans Narrow’. To make them appear a bit smaller than their descriptions, we’ll set their font-size to 80%. Then, we’ll convert our description terms (Client and Role) into uppercase using ‘text-transform:uppercase’. We’ll also set a negative bottom margin to avoid excessive spacing between the terms and their descriptions.

Definition Descriptions

We don’t need to do much here – we’ll just set the margin to 0. (By default, <dd>s have a slight left margin.)

Step 15: CSScurrent’/‘not-current’ Thumbnails


When the thumbnails of a certain category are given the .current class (through JS), we want them to grow and get their normal tone back (i. e. remove the sepia). We’ll scale them up by using ‘transform: scale(1.05)’, thus scaling it to 1.05 times the original size on both the x and y axes and remove the sepia we had added earlier by using ‘-webkit-filter: sepia(0)’.


Here, we’ll do the exact opposite of what we did to the .current thumbnails — we’ll scale them down to 75% using ‘transform: scale(0.75)’ and make them black and white using ‘-webkit-filter: grayscale(1)’.


We’ll simply set the text color of the ‘.current-li’s to black.

Step 16: The Complete CSS

Here’s what our completed CSS looks like:

Let's start working on the JS now.

Step 17: JS The Algorithm

Here is what we’re going to do through our Javascript (in chronological order):

  1. Detect nav > li press.
  2. Scale down all the thumbnails by giving them the .not-current class.
  3. Add the .current-li class to the selected category’s corresponding <li>.
  4. Remove the .not-current class only from the thumbnails of the selected category.
  5. Add the .current class to all the thumbnails of the selected category.

#2 here will be done using the scaleDown() function and #3, #4, and #5 will be done using the show(category) function.

Step 18: JS The scaleDown() Function

Using the removeClass and addClass methods, we’ll remove the .current class from the elements which have it and add the .not-current class to all of them. We’ll also remove the .current-li class from any <li which currently has it.

Step 19: JS The show(category) Function

This function will be implemented each time an <li> is clicked. First, we’ll call the scaleDown() function to scale down all the thumbnails. Then, we’ll add the .current-li class to the <li which corresponds to the selected category. We’ll then change the class of the category’s thumbnails from .not-current to .current (we had applied the .not-current class to all the thumbnails in the scaleDown() function). Finally, if the selected category is ‘all’, we’ll remove the dynamically added classes (i.e. .current and .not-current) from all the thumbnails.

Note: Since the name of the id (of the <li) and class (of the figures) of each category is the same, we’ll simply refer to the <li as ‘# + category’ and the figures as ‘. + category’.

Step 20: JS Detecting Clicks and Implementing the show(category) Function

Finally, through the document.ready method, we’ll add the .current-li class to li#all and detect nav > li clicks. We’ll then get the id of the clicked <li and implement the show(category) function where the ‘category’ will be ‘’ i.e. id of the clicked <li>. So, for example, if the <li> with the id #print is clicked, show(‘print’) will be implemented.

This completes our Javascript.

Step 21: The Complete JS

Our completed JS looks like this:

Now that our site is fully functional, let’s make it responsive.

Step 22: CSS Making it Responsive

Let’s open ‘media-queries.css’ and get going. How you choose to implement your media queries is entirely up to you. You may prefer to have media queries within your main stylesheet, you may even prefer to have them modular and inline with each and every style declaration - it's up to you!

Style changes required for each breakpoint are described here.

965px or less

  • Decrease the width of the container to 840px
  • Decrease the height and width of the thumbnails to 170px each so as to accommodate 4 thumbnails in each row [(170px + 40px) x 4 = 210px x 4 = 840px]

860px or less

  • Decrease the width of the container to 720px
  • Increase the height and width of the thumbnails back to 200px each to accommodate 3 in each row [(200px + 40px) x 3 = 240px x 3 = 720px]

740px or less

  • Decrease the width of the container to 600px
  • Decrease the opacity of the .not-current to 50% (since we're mainly working for mobile devices now)
  • Decrease the height and width of the thumbnails to 160px each to accommodate 3 in each row [(160px + 40px) x 3 = 200px x 3 = 600px]

610px or less

  • Decrease the width of the container to 460px
  • Set the top and bottom margin of the thumbnails to 20px and left and right margin to 60px
  • Increase the height and width of the thumbnails back to 200px each to accommodate 1 in each row [(200px + 120px) x 1 = 320px x 1 = 320px]

480px or less

  • Decrease the width of the container to 320px
  • Set the top and bottom margin of the thumbnails to 20px and left and right margin to 60px
  • Increase the height and width of the thumbnails back to 200px each to accommodate 1 in each row [(200px + 120px) x 1 = 320px x 1 = 320px]

Browser Compatibility

The basic scaling functionality (CSS transforms) works perfectly in most major browsers, which include:

  • IE 9+ (use for lower versions)
  • Firefox 14+
  • Chrome 21+
  • Safari 5.1+

The filter effects (sepia and grayscale) work only in Webkit browsers.


I’ve made three jsFiddles for you to try out and experiment with:

  1. Caption Split Effect (Vertical)
  2. Caption Split Effect (Horizontal)
  3. Diagonal Masked Thumbnail Images


That’s it! We have successfully created a working, filterable, and responsive portfolio. I hope you found this tutorial useful. Thanks for reading!