# 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.

---

LLMS index: [llms.txt](/llms.txt)

---

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 {#enable}

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:

```yaml {title="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:

```yaml {title="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 (`products` → `Products`). To name one
yourself, write `title` / `linkTitle` in `content/<plural>/_index.md` and
`_index.zh.md`, and the theme prefers it:

```yaml {title="content/modules/_index.md"}
---
title: Product modules
linkTitle: Modules
---
```

## Tagging a page {#assign}
The front matter key is the **plural** (the value column of `taxonomies`), and
the value is always a list, even with one entry:

```yaml {title="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:

```yaml {title="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.

## The term line on a page {#page-header}

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:

```yaml {title="hugo.yml"}
params:
  taxonomy:
    page_header: [categories]
```

The key is catalogued in [Configuration](/docs/customize/config/). It cannot be
used to hide the line — see [Limits](#limits).

## Two taxonomies the theme knows by name {#authors}

`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:

```yaml {title="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](/docs/write/blog/#authors). 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 {#term-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](https://gohugo.io/content-management/taxonomies/#add-custom-metadata-to-a-taxonomy-or-term)
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 {#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:

```yaml {title="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.

## The taxonomy panel in the navbar {#navbar-panel}

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:

```yaml {title="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](/docs/customize/navigation/).

## Bilingual terms {#bilingual}

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:

```yaml {title="content/docs/ha/patroni.md"}
categories: [High availability]
tags: [PostgreSQL, Patroni, failover]
```

```yaml {title="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](/docs/customize/i18n/).

## Switching by content type {#per-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 {#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/](/categories/) showing filter chips for every term, each leading to its term page.

On the command line, check the output:

```bash
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:

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

## Limits {#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.

## Related {#related}

- [Page parameters](/docs/write/frontmatter/) — `categories` / `tags` and the other front matter keys
- [Blog posts](/docs/write/blog/) — how the blog list works with taxonomies
- [Navigation and menus](/docs/customize/navigation/) — how to write navbar entries
- [Languages](/docs/customize/i18n/) — per-language content and menus
- [Configuration](/docs/customize/config/) — full definitions of `params.taxonomy.*` and `params.ui.taxonomy_icons`
