How to implement Tags at Jekyll website

ear tag

Recently I have finished adding tagging infrastructure into this website. That’s was a bit tricky since Jekyll does not fully support this feature from the box. It is possible to add tags into the post’s YAML front matter and then access them using page varialbes via page.tags. However, there are no built-in means to generate tags page which collects all tags from the posts, sorts them alphabetically and builds a list of the posts assigned to every tag. Let’s take a look at one possible way to implement described functionality.

Creating Tags boilerplate

First, let’s decide how our tags will look and feel. I’ve chosen CSS tags by Wouter Beeftink. Let’s add them to our site’s CSS. HTML part contains example on how to add a single tag or a list of tags to the page. Let’s grab it and modify a bit using Jekyll templating language - Liquid. I want to see tags at both index page and at each post page, so I added following code snippets:

index.html

<ul class="tags">
{% for tag in post.tags %}
  <li><a href="/tags#{{ tag }}" class="tag">{{ tag }}</a></li>
{% endfor %}
</ul>

_layouts/post.html

<ul class="tags">
  {% for tag in page.tags %}
    <li><a href="/tags#{{ tag }}" class="tag">{{ tag }}</a></li>
  {% endfor %}
</ul>

This assumes that we have tags page in the root of our Jekyll project. Let’s create dummy tags page for a time being:

$ cd your_jekyll_project
$ touch tags.md

Later we will fill it with the code to generate a list of tags and related posts. To see the tags at the index page and post page we have to tag our posts. It is done using tags variable inside each post’s YAML front matter. As an example, this post’s tags variable is as follows:

tags: jekyll liquid blogging

After adding tags to posts we should be able to see shiny new tags at our pages.

Tags page

Our tags page should:

  1. List all of the tags from the site alphabetically.
  2. Assign each post with specific tag to the appropriate tag section.

As an example look here: http://pavdmyt.com/tags/.

This task requires some advanced knowledge of Liquid. But fortunately I found this article, which helped me a lot. I modified provided example for my needs, but main logic remains the same. So, basically our tags.md consists from 3 parts:

  • Building a sorted array of the tag names.
<!-- Get the tag name for every tag on the site and set them
to the `site_tags` variable. -->
{% capture site_tags %}{% for tag in site.tags %}{{ tag | first }}{% unless forloop.last %},{% endunless %}{% endfor %}{% endcapture %}

<!-- `tag_words` is a sorted array of the tag names. -->
{% assign tag_words = site_tags | split:',' | sort %}
<!-- List of all tags -->
<ul class="tags">
  {% for item in (0..site.tags.size) %}{% unless forloop.last %}
    {% capture this_word %}{{ tag_words[item] }}{% endcapture %}
    <li>
      <a href="#{{ this_word | cgi_escape }}" class="tag">{{ this_word }}
        <span>({{ site.tags[this_word].size }})</span>
      </a>
    </li>
  {% endunless %}{% endfor %}
</ul>
  • Creating tag sections with related posts.
<!-- Posts by Tag -->
<div>
  {% for item in (0..site.tags.size) %}{% unless forloop.last %}
    {% capture this_word %}{{ tag_words[item] }}{% endcapture %}
    <h2 id="{{ this_word | cgi_escape }}">{{ this_word }}</h2>
    {% for post in site.tags[this_word] %}{% if post.title != null %}
      <div>
        <span style="float: left;">
          <a href="{{ post.url }}">{{ post.title }}</a>
        </span>
        <span style="float: right;">
          {{ post.date | date_to_string }}
        </span>
      </div>
      <div style="clear: both;"></div>
    {% endif %}{% endfor %}
  {% endunless %}{% endfor %}
</div>

More details and explanations could be found in the mentioned article. A good reference on Liquid tags and filters available in Liquid for Designers. Jekyll-specific Liquid tags and filters described in Jekyll docs.

Wrapping Up

That’s all we need! Feel free to dig around source code to understand how tags are implemented here.

Header image is a resized Photo by Dave Young, available under a Creative Commons Attribution 2.0 Generic license.

Previous post: Digging around factorial function

Next post: Python solutions for the Project Euler (problems 1-10)