Skip to content

Taxonomies

Give pages a second index that cuts across the directory tree with tags and categories — term pages, filter chips, the rail cloud and the navbar panel are all automatic.

A directory tree gives a page one path; a taxonomy gives it a second. The same PostgreSQL backup document sits under an “Operations” directory and is also reachable from a “backup” tag page. Enabling it needs only Hugo’s taxonomies: configuration: the term pages, filter chips, rail cloud and navbar panel are all generated by the theme, with no template to write.

This page carries a category. The “Categories: Customization” line under the title, and the counted chips under the outline in the right column, need no configuration on the page itself.

Enabling a taxonomy

Taxonomies are Hugo’s, and the theme adds no switch of its own. Write taxonomies: at the top level of hugo.yml, with the singular name as the key and the plural as the value:

hugo.yml
taxonomies:
  tag: tags
  category: categories

That is this site’s configuration. Three things to note:

  • Writing taxonomies: makes it the complete list, not an addition. To keep tags / categories alongside a custom taxonomy, list them too.
  • The plural is also the URL segment: /tags/, /categories/.
  • To turn them all off: disableKinds: [taxonomy, term].

Adding one of your own, for instance grouping by product module:

hugo.yml
taxonomies:
  tag: tags
  category: categories
  module: modules

Display names: the six keys tag, tags, category, categories, module, modules have a localized title in every one of the theme’s language files. Any other taxonomy uses the humanized plural (productsProducts). To name one yourself, write title / linkTitle in content/<plural>/_index.md and _index.zh.md, and the theme prefers it:

content/modules/_index.md
---
title: Product modules
linkTitle: Modules
---

Tagging a page

The front matter key is the plural (the value column of taxonomies), and the value is always a list, even with one entry:

content/docs/ha/patroni.md
---
title: Patroni high availability
description: Managing PostgreSQL failover with Patroni.
categories: [High availability]
tags: [PostgreSQL, Patroni, failover]
---

Where a whole section shares one category, write it in the section index’s cascade rather than repeating it on every page:

content/docs/customize/_index.md
---
title: Customization
linkTitle: Customization
icon: fa-solid fa-sliders
cascade:
  categories: [Customization]
---

All six documentation sections on this site are configured that way. A page’s own categories: replaces the cascade rather than merging with it: to add one beside the section’s category, write both.

Documentation and blog pages render a line of assigned terms under the title and summary, each linking to its term page — the “Categories: Customization” at the top of this page. Its container is .taxonomy-terms-article, with an additional .taxo-<plural> class per taxonomy; use those two selectors to style it.

By default it lists every taxonomy on the page, except the two reserved plurals authors and series — each of those has a surface of its own (a byline and a series strip), so repeating them as chips would say the same thing twice. Naming one in page_header puts it back.

To show only some, in a fixed order:

hugo.yml
params:
  taxonomy:
    page_header: [categories]

The key is catalogued in Configuration. It cannot be used to hide the line — see Limits.

Two taxonomies the theme knows by name

authors and series are ordinary Hugo taxonomies, declared the ordinary way — the theme adds no parameter for either. What it adds is a rendering of each, so the declaration alone is the whole switch:

hugo.yml
taxonomies:
  category: categories
  tag: tags
  author: authors
  series: series
Plural What the declaration turns on The term page becomes
authors Portraits and linked names in the article head, names on list rows, one <dc:creator> per author in the feed The author’s profile: the display name is the term page’s link title (linkTitle, else title), description the one-line introduction, the body the long one, and the avatar whatever the featured-image resolver picks
series A strip above the article body naming the series, this article’s position, the next part, and the whole list behind a <details> The series introduction, listing its members in reading order rather than newest-first

Both are covered in full, with the front matter each expects, in Writing a blog. Two things worth knowing here:

  • There is deliberately no data/authors file. The profile is the term page, so nothing can disagree with it.
  • A series term page is the one term page that is not in reverse-date order. Members with a series_weight come first in ascending order, the rest by ascending date. A term page cannot supply an order to Hugo, so the theme resolves it once and both surfaces read the result.

Term and taxonomy pages

Each taxonomy generates two levels of page:

Page URL Contents
Taxonomy list /categories/ Titled with the taxonomy’s localized name (“Categories”), followed by filter chips for every term, each with a count, the first being “All”
Term page /categories/customization/ Titled “Categories: Customization”, listing every page with that term newest first, styled like the blog list

A Chinese term’s URL uses Chinese characters (the address bar shows 定制站点 and the HTML is percent-encoded); Hugo does not transliterate. Where ASCII URLs are wanted, use English terms and give each a display name with title in content/categories/<term>/_index.zh.md — Hugo’s term page content file mechanism.

A term page has no fixed place in the content tree, so it borrows one: when every member of a term sits under one top-level section, the term page renders that section’s sidebar tree and root link, and a reader clicking a tag from the documentation stays inside the documentation navigation. Where members span sections, it falls back to the site-level tree. The “All” filter chip follows the same rule: with one section it points at that section’s index, and across sections at the taxonomy list page.

Filter chips appear on the taxonomy list page only; term pages carry the rail cloud instead.

The rail cloud

Documentation, blog and term pages carry one group per taxonomy in the right column (under the outline), with counted, collapsible chips. The group is automatic and has no switch: it appears wherever a taxonomy is defined and the current scope has terms.

The count is not site-wide but per top-level section: it first looks for a section named after the page’s type (a type: docs page uses the /docs/ tree), and otherwise uses the top-level section the page is in. “Tags: release 4” on a blog page means four posts in the blog, not four on the site.

Icons are configured by plural name:

hugo.yml
params:
  ui:
    taxonomy_icons:
      categories: fa-solid fa-folder
      tags: fa-solid fa-tags
      modules: fa-solid fa-cubes

Those two are already the defaults for categories and tags; any other taxonomy defaults to fa-solid fa-shapes. An icon is one Font Awesome class pair, written as everywhere else on the site.

A main-menu entry pointing at a taxonomy list page automatically becomes a panel of term chips (by descending usage, with counts), needing no hand-written dropdown:

hugo.yml
languages:
  en:
    menus:
      main:
        - identifier: tags
          name: Tags
          pageRef: /tags
          weight: 60

Both pageRef: /tags and the older url: /tags/ are recognized: a URL-form menu entry is resolved to a site page before its kind is judged, so migrating from an older configuration needs no rewrite. Other ways to write menus are in Navigation and menus.

Bilingual terms

Hugo counts and links taxonomies per language: /categories/ and /zh/categories/ are two unrelated trees, and a Chinese page enters only the Chinese one. Terms are written once per language in each front matter:

content/docs/ha/patroni.md
categories: [High availability]
tags: [PostgreSQL, Patroni, failover]
content/docs/ha/patroni.zh.md
categories: [高可用]
tags: [PostgreSQL, Patroni, 故障切换]

Two things to watch:

  • The same word written identically in both languages (say release) still yields two term pages, /categories/release/ and /zh/categories/release/, each counting only its own language’s pages. Do not write English terms on Chinese pages for the sake of uniformity: the rail chips would then show English.
  • A taxonomy’s display name follows the language (for the six built-in keys), but a term’s name does not: a term is exactly the string written in front matter, and the theme does not translate it. Write 高可用 on an English page and the English site’s chip reads 高可用.

The rest of running a multilingual site is in Languages.

Switching by content type

The theme has no “show on documentation, hide on blog” switch; the control is which pages you tag. What this site does:

Content categories tags Effect
content/docs/** Section-level cascade (the six sections) none The term line has one row, Categories
content/blog/** Per post (release, oink) Per post (Oink, Release) Two rows in the term line, two chip groups in the rail

To make a whole section disappear from the taxonomy, delete categories from the section index’s cascade; nothing else is needed. To keep one page out, write categories: [] in its own front matter — an empty list overrides the cascade.

Verify

Three things to look at on the page:

  • A “Categories: Customization” line under this page’s title;
  • Chips grouped by taxonomy under the outline in the right column, each with a count;
  • /categories/ showing filter chips for every term, each leading to its term page.

On the command line, check the output:

hugo -d public
ls public/categories/          # one directory per term
grep -c 'taxonomy-term' public/docs/customize/index.html

The theme repository has a dedicated check verifying that no taxonomy pages are generated without taxonomies:, and that term page titles are correct in both languages:

cd ~/pgsty/oink && python3 bin/check-taxonomy.py

Limits

  • page_header: [] does not hide the term line: an empty list is treated as unset and falls back to “list every taxonomy”. To remove the line, stop tagging those pages, or hide .taxonomy-terms-article in assets/scss/_styles_project.scss.
  • The rail cloud has neither a switch nor a cap; a site with very many terms should reduce its taxonomies, as there is no way to trim it in configuration.
  • Term pages have no cross-language pairing: switching language on a term page does not guarantee landing on the same term in the other language.