Table of Contents

Grouping Hugo tags by first letter

We will probably never get .Pages.GroupBy "title.firstLetter".

5 Jul 2021. 804 words.


Update This post is from the old theme, so the screenshots will look different to what you see here.

Introduction

Hugo provides a powerful content management tool called taxonomies. In addition to default taxonomies such as categories and tags, you can define one on your own and customise as you like (see this repo for a cool example!).

Problem

One important feature of taxonomies is that you can show the list of terms (keywords) across the site, such as the list of tags. By default, the list looks like a regular branch bundle of a section because it follows the same layout:

The list of all tags in this blog using the default list layout.

Now, how can we improve this page? We can certainly reduce the spacing between terms using flexbox and sort them alphabetically.

The list of all tags in this blog using a custom layout with alphabetical sorting.

It looks much better than before, but you can expect this list will grow to a messy clutter of words as the number of tags increases over time. It would look much better if we could divide them into smaller chunks based on the first letter.

The list of all tags in this blog grouped by the first letter.

note In Hugo, a taxonomy consists of many terms. So, the layout for the full list of tags should be tags/taxonomy.html, and the layout for a single tag should be named as tags/term.html. See below where Hugo pulls the layout from depending on the folder structure. For the full picture, check the documentation.

.
└── layouts/
    ├── _default/
    │   ├── baseof.html
    │   ├── list.html      <- /posts
    │   │                  <- /tags, /tags/cool-tag
    │   │                  <- /categories, /categories/Hugo
    │   └── single.html    <- /posts/my-first-post
    └── tags/
.
└── layouts/
    ├── _default/
    │   ├── baseof.html
    │   ├── list.html      <- /posts
    │   │                  <- /categories, /categories/Hugo
    │   └── single.html    <- /posts/my-first-post
    └── tags/
        └── taxonomy.html  <- /tags, /tags/cool-tag
.
└── layouts/
    ├── _default/
    │   ├── baseof.html
    │   ├── list.html      <- /posts
    │   │                  <- /categories, /categories/Hugo
    │   └── single.html    <- /posts/my-first-post
    └── tags/
        ├── taxonomy.html  <- /tags
        └── term.html      <- /tags/cool-tag

Approach

I want to clarify that this is not the first solution to this problem. There was a discussion on this matter a while ago, and you can also find a different solution. These methods basically sort the titles alphabetically, track the first letter, and start the <ul> element again whenever it detects a change. I wanted to try a more conventional approach, where you first build a dictionary of tags that look like this:

{
  "A": [
    "absolute value",
    "algebra"
  ],

  "D": [
    "decimal",
    "distributive law"
  ],

  // ...
}

and build the document using this information.

Grouping the items

We will first build the dictionary, which we call $by_letter, where the keys are the letters of the alphabet, and the values are the list of tags that start with that letter. Here is the full code:

layouts/_default/taxonomy.html
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
{{ define "main" }}
{{ $letters := split "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "" }}
{{ $pages := .Pages.ByTitle }}
{{ $by_letter := dict }}
{{ range $pages }}
  {{ $page := . }}
  {{ $first_letter := upper ( substr $page.Name 0 1 ) }}
  {{ if not (in $letters $first_letter) }}
    {{ $first_letter = "#" }}
  {{ end }}
  {{ $new_list := slice $page }}
  {{ with index $by_letter $first_letter }}
    {{ $new_list = . | append $page }}
  {{ end }}
  {{ $by_letter = merge $by_letter (dict $first_letter $new_list) }}
{{ end }}
<!-- ...rendering happens here -->
{{ end }}

Rendering

We can then make the list from the dictionary. Since we will make use of the CSS grid, the keys can just sit inside a <span>.

layouts/_default/taxonomy.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{{ define "main" }}
<!-- ...sorting happened here -->
<div class="taxonomy">
{{ range $key, $items := $by_letter }}
  <span class="key">{{ $key }}</span>
  <ul>
  {{ range $items }}
    <li>
      <a href="{{ .RelPermalink }}">{{ .Name }}</a><sup>{{ len .Pages }}</sup>
    </li>
  {{ end }}
  </ul>
{{ end }}
</div>

Finally, we can put everything together using CSS.

assets/scss/main.scss
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
.taxonomy {
  display: grid;
  grid-template-columns: 2rem 1fr;
  align-items: baseline;

  .key {
    font-style: italic;
    font-size: 1.7rem;
  }
  ul {
    display: flex;
    flex-wrap: wrap;
    list-style: none;
  }
  li {
    margin-right: 1em;
  }
}