One of the things I'm often looking for but can't find is a basic filtering system for portfolio items. It seems like the ones I find online are too complicated or bloated. They're trying to do way too much. So I created a basic framework for a portfolio filtering system that works great as-is. You can also fork it and add any bells and whistles you want.

HTML

  <main>
  <nav id="controls">
    <ul>
      <li class="filter filter-all active">All</li>
      <li class="filter filter-landscape">Landscape</li>
      <li class="filter filter-urban">Urban</li>
      <li class="filter filter-portrait">Portrait</li>
    </ul>
  </nav>
  <section class="grid-wrapper">
    <div class="image landscape landscape-1 all"></div>
    <div class="image urban urban-1 all"></div>
    <div class="image urban urban-2 all"></div>
    <div class="image portrait portrait-1 all"></div>
    <div class="image urban urban-3 all"></div>
    <div class="image portrait portrait-2 all"></div>
    <div class="image urban urban-4 all"></div>
    <div class="image landscape landscape-2 all"></div>
    <div class="image portrait portrait-3 all"></div>
    <div class="image landscape landscape-3 all"></div>
    <div class="image landscape landscape-4 all"></div>
    <div class="image portrait portrait-4 all"></div>
  </section>
</main>

I'm using div tags here but you could just as easily make those more semantic. We essentially have two sections: the navigation (#controls) and the grid (.grid-wrapper). I'm attaching the class .filter to the individual navigation elements. Each of the elements also has a specific class of .filter-[name] attached. We will use this to connect to the appropriately labeled items in the grid. There is also an .active class that will be applied to the element the user clicks.

The grid is a collection of images. I'm using the background-image property to serve the images to the appropriate cells. You could go with hard-coded images using the img tag, too. The images share a class called .image that gives some general values for all the images. Each image also has a class that represents its filter type: urban, portrait, landscape, all. These filter types are what I will use to hook the filtering system to. Finally, each image has a unique class that allows me to apply the background image to a specific grid cell.

CSS

  * {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
}

.hide {
  -webkit-animation: fadeOut 150ms ease;
          animation: fadeOut 150ms ease;
  opacity: 0;
}

@-webkit-keyframes fadeOut {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

@keyframes fadeOut {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
.hide {
  display: none;
}

#controls {
  margin-bottom: 2rem;
}
#controls ul {
  list-style: none;
  padding: 1rem;
  margin: 0;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
}
#controls ul li {
  margin: 0;
  padding: 0.5rem;
  border: 1px solid #eee;
  cursor: pointer;
}
#controls ul li:hover {
  background: #eee;
}
@media (min-width: 400px) {
  #controls ul li {
    margin-right: 1rem;
    padding: 1rem;
  }
}
#controls ul .active {
  background: #444;
  color: white;
}
#controls ul .active:hover {
  color: #444;
}

.grid-wrapper {
  display: -ms-grid;
  display: grid;
  -ms-grid-columns: (1fr)[1];
      grid-template-columns: repeat(1, 1fr);
  -ms-grid-rows: auto;
      grid-template-rows: auto;
  grid-gap: 1rem;
  -ms-grid-column-align: center;
      justify-items: center;
  margin: 1rem;
}

@media (min-width: 500px) {
  .grid-wrapper {
    -ms-grid-columns: (1fr)[2];
        grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 768px) {
  .grid-wrapper {
    -ms-grid-columns: (1fr)[3];
        grid-template-columns: repeat(3, 1fr);
  }
}
@media (min-width: 1368px) {
  .grid-wrapper {
    -ms-grid-columns: (1fr)[4];
        grid-template-columns: repeat(4, 1fr);
  }
}
.image {
  width: 100%;
  height: 50vh;
}

@media (min-width: 500px) {
  .image {
    width: 100%;
    height: 50vw;
  }
}
@media (min-width: 768px) {
  .image {
    width: 100%;
    height: 40vw;
  }
}
@media (min-width: 1368px) {
  .image {
    width: 100%;
    height: 20vw;
  }
}
.urban-1 {
  background: url(https://unsplash.it/1500?image=1075);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

.urban-2 {
  background: url(https://unsplash.it/1500?image=1067);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

.urban-3 {
  background: url(https://unsplash.it/1500?image=1033);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

.urban-4 {
  background: url(https://unsplash.it/1500?image=983);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

.landscape-1 {
  background: url(https://unsplash.it/1500?image=961);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

.landscape-2 {
  background: url(https://unsplash.it/1500?image=916);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

.landscape-3 {
  background: url(https://unsplash.it/1500?image=901);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

.landscape-4 {
  background: url(https://unsplash.it/1500?image=876);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

.portrait-1 {
  background: url(https://unsplash.it/1500?image=978);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

.portrait-2 {
  background: url(https://unsplash.it/1500?image=996);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

.portrait-3 {
  background: url(https://unsplash.it/1500?image=1027);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

.portrait-4 {
  background: url(https://unsplash.it/1500?image=838);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

The CSS may be a little complicated if you're new to CSS3. I'm using some advanced concepts like keyframe animation and background images. Let me walk you through the different parts.

After setting up some basics with the body tag, I went right into an animation. This was the original concept. When you run the code, however, the second .hide class is overriding the animation. So if you want to see the items animate out, just comment out the second .hide class. The animation is pretty basic and just covers the opacity. You could also change the visibility property so that the items are no longer taking up space on the grid. If you don't bring down the opacity all the way to '0' then it has a nice highlighting effect. I've used this technique on my own portfolio. In fact, I'm using this exact structure to create the portfolio grid on that page.

Next is the navigation. This is just a simple menu that is being inlined using Flexbox. If you don't know much about Flexbox, check out my video tutorials on YouTube to get acquainted. You should be using it a lot in your work, as it's now almost universally supported and will give you more control over the alignment of elements. You can see that I've styled the .active class here which is switched (through javascript) when the user clicks on a navigation item.

The final piece is the grid itself. I'm using CSS Grid Layout to set the structure of the grid. It's a fairly simple grid with equal sized columns and rows. I've set media queries to allow for more columns as the screen size gets larger. If you don't know about CSS Grid, I've also recorded an introductory video tutorial series that covers everything you need to know to get started with CSS Grid Layout. It's not universally supported, but is supported in the latest versions of most major browsers. For more complete browser support, check out this fallback cheatsheet from Rachel Andrew.

I've used the section with the class of .grid-wrapper to be the grid container. You could use an unordered list to be the grid container. This might make it more semantic. When that container is set to display: grid then all of its children become grid items. That's one of the reasons using an unordered list makes sense.

Finally, I set the height and width of each image. You can play around with the sizes here to fit your needs. I wanted to make sure the images were neither overwhelming nor too small, so there are several size adjustments as the screen gets larger. You can also see that each cell is being targeted specifically so that I can flow an image into the cell. If you're using an img tag for your images, these are unnecessary.

JavaScript (jQuery)

  // Show all
$('.filter-all').on('click', function() {
  var $this = $(this);
  $('.filter').removeClass('active');
  $this.addClass('active');
  $('.all').removeClass('hide');
});

// Show landscape
$('.filter-landscape').on('click', function() {
  var $this = $(this);
  $('.filter').removeClass('active');
  $this.addClass('active');
  $('.landscape').removeClass('hide');
  $('.urban, .portrait').addClass('hide');
});

// Show urban
$('.filter-urban').on('click', function() {
  var $this = $(this);
  $('.filter').removeClass('active');
  $this.addClass('active');
  $('.urban').removeClass('hide');
  $('.portrait, .landscape').addClass('hide');
});

// Show portrait
$('.filter-portrait').on('click', function() {
  var $this = $(this);
  $('.filter').removeClass('active');
  $this.addClass('active');
  $('.portrait').removeClass('hide');
  $('.landscape, .urban').addClass('hide');
});

The jQuery is simple. I'm just hiding and showing different items based on which navigation button the user clicks. On click, I record the button in the variable $this and then switch the .active class to the filter button that the user clicked. The category the user wants to see is shown while the other categories are hidden.

I'm sure there are ways to make this more DRY. I'm no code poet when it comes to JS. You could also use vanilla JS for this. It would save some page size if you didn't want to use the jQuery library.

And that's it. Easy peasy. You can take this basic framework and add hover effects, animation effects, or anything else your heart desires. If you just need a quick and dirty filtering system, though, this will work for you just fine. It's small, straight forward, responsive, and scalable.

View on Codepen