Filtering Blog Posts by Category With Jekyll

by Jeff Langr

March 26, 2020

I’ve just started to learn Jekyll as I rework my site. With over a couple hundred blog posts that cover a few dozen categories, it was important to present the ability for a user to see lists of blog entries by category.

The general idea:

  • Render a series of buttons (which could also be text links), one for each category.

  • On clicking a button, call a function that iterates through all site posts. If the clicked category appears in a given post’s list of categories, display the corresponding blog entry, otherwise remove it.

Sounds easy, right? The resulting code is fairly simple; getting there was just a little trickier. I’ll lay it out piece-by-piece, then provide the entire page source.

Displaying the series of category buttons is mostly Liquid + HTML:

<button id="All" onclick="filterUsingCategory('All')">
  *Show All Posts*
{% assign categories = site.categories | sort %}
{% for category in categories %}
  {% assign cat = category | first %}
  <button id="{{ cat }}" onclick="filterUsingCategory(">
    {{ cat }}
{% endfor %}

The Jekyll docs aren’t terribly clear, but looking at the source reveals that site.categories is a key-value-pair data structure (a Ruby Hash), where the key is the category name and the value is the list of posts. Sorting site.categories thus sorts by the key, which is the category name. Applying the first filter to category returns that category name.

Each button gets an id which is the unique category name itself. When clicked, the button’s id gets passed to the filterUsingCategory JavaScript function.

Displaying the list of all posts requires only a small change:

{% assign id = 0 %}
{% for post in site.posts %}
  {% assign id = id | plus:1 %}
  <div class="post" id="{{id}}">
    <p class="itemInteriorSection">
      <a href="{{post.url}}">{{ post.articletitle }}</a><br />
      <!-- more post details here -->
{% endfor %}

Each post needs a unique ID so that its div (containing the blog summary entry) can be found and either shown or not. The easiest technique was to track an id that increments each iteration through the Liquid loop {% for post in site.posts %}.

The JavaScript filter function mixes & matches Liquid and JavaScript:

<script type="text/javascript">
  function filterUsingCategory(selectedCategory) {
    var id = 0;
    {% for post in site.posts %}
      var cats = {{ post.categories | jsonify }}

      var postDiv = document.getElementById(++id); =
        (selectedCategory == 'All' || cats.includes(selectedCategory)) 
          ? 'unset' 
          : 'none';
    {% endfor %}

The category selected by the user (by clicking on one of the buttons) is passed as an argument to filterUsingCategory.

Code in the function iterates through site.posts and once again tracks an ID that increments each time through the loop, so that we can retrieve each post’s corresponding div. For each post, the first step is to convert the Liquid array of posts to a JavaScript array by using the jsonify filter.  Next, the post’s div element is retrieved by using the (pre-incremented) id. Finally, the div is either included if the category is 'All' or if the post’s categories contains the selected category.

You’ll want to add appropriate styling of course.

I’ve posted a gist of my blog page’s It contains all of the above code in context and in an appropriate order.

Share your comment

Jeff Langr

About the Author

Jeff Langr has been building software for 40 years and writing about it heavily for 20. You can find out more about Jeff, learn from the many helpful articles and books he's written, or read one of his 1000+ combined blog (including Agile in a Flash) and public posts.