Authoring

Navigation model

How the sidebar, pager, and home listing decide what to show.

5 min

Five rules cover 100% of the theme’s navigation behavior. Once you know them, you can predict exactly where any page will appear.

Rule 1 — Order is by weight, then title

Every list (sidebar, pager, card grid, search index) is sorted ascending by weight. Pages with the same weight are sorted by title next, then by file path.

The convention used throughout the theme is weight = 10, 20, 30, … so you can insert pages without renumbering.

Rule 2 — hidden: true excludes the page from listings

When a page has hidden: true in front matter:

Use this for draft pages, internal references, or any page you want reachable by URL but not advertised.

Rule 3 — Each section is its own pager scope

The pager (prev/next) walks pages within the current section only. From the last page of chapter A, Next is empty (not the first page of chapter B). From the first page of chapter B, Previous goes back to the chapter B landing — not into chapter A.

This trades continuous flow for predictable bounded navigation. Readers use the sidebar (or / search) to jump between chapters.

If you want to opt a page out of the pager entirely, set nopager: true.

Rule 4 — Section pages are nav stops

A _index.md is a navigable page. The pager treats it as the first item of its scope:

This means your chapter _index.md content matters — it’s the first thing readers hit when they enter the chapter, before any lesson.

Rule 5 — The home picks top-level sections

/ (the home page) lists every top-level section in the site, sorted by weight, hidden filtered. Override the order or pick specific sections by setting it — either in your site config (homeSections, camelCase) OR on the home page’s _index.md front matter (home_sections, snake_case; the page form wins when both are set):

toml
# hugo.toml — [params] uses camelCase
[params]
  homeSections = ["workshops", "guides", "reference"]
toml
# content/_index.md — front matter uses snake_case
+++
home_sections = ["workshops", "guides", "reference"]
+++

Without either, the home shows all top-level sections in weight order. See Customizing › home sections for the full contract (typo warnings, hidden filtering).

Worked example

Given:

text
content/
├── _index.md                                           # weight default
├── workshops/
│   ├── _index.md                                       # weight = 1
│   ├── getting-started/
│   │   ├── _index.md                                   # weight = 1
│   │   ├── 01-introduction.md                          # weight = 10
│   │   ├── 02-installation.md                          # weight = 20, hidden = true
│   │   └── 03-first-search.md                          # weight = 30
│   └── advanced/
│       ├── _index.md                                   # weight = 2
│       └── 01-pipelines.md                             # weight = 10
└── about.md                                            # weight default

Then:

Where you areWhat you see
/Cards: workshops, about (sorted by weight, then title)
/workshops/Cards: getting-started, advanced
/workshops/getting-started/Sidebar: 01-introduction, 03-first-search (02 is hidden)
/workshops/getting-started/01-introduction/Prev: chapter landing · Next: 03-first-search (skips hidden 02)
/workshops/getting-started/03-first-search/Prev: 01-introduction · Next: empty

That’s the entire model.

Opt-in: a “See all” directory page

For sites with many workshops across several categories, a single browse page at /browse/ lists every workshop in one place — grouped by top-level category, with title + description + last-updated time per workshop, a “Recently updated” rail at the top, and a live client-side filter. Each row links to the workshop itself; chapter pages stay hidden behind that link (clicking enters the workshop where the existing pager takes over).

Create content/<lang>/browse/_index.md (or content/browse/_index.md on a single-language site):

toml
+++
title       = "All Workshops"   # markdownified — `All *Workshops*` renders the italic word as gradient
description = "Every workshop in one place."
layout      = "browse"
weight      = 5
[menu.main]
  weight = 5

# Optional: nominate which top-level sections to surface (and in what order).
# Omit to auto-include every visible top-level section that contains at
# least one workshop.
browse_sections = ["workshops", "scenarios", "docs"]
+++

layout = "browse" is what activates the directory view; without it, the page falls back to the normal section layout. The [menu.main] block adds it to the header navigation.

Granularity. A “workshop” here means a leaf workshop root — the topmost non-hero section in a branch. Category hubs (layout = "hero") are descended through; their child workshops surface as rows. Chapter pages within a workshop are never listed individually.

Recently updated rail. The three most recently changed workshops (by git Lastmod) show as chips at the top. This requires enableGitInfo = true in your hugo.toml — without it, every page reports the same zero timestamp and the rail picks arbitrary entries.

Search overlay integration. The header’s / search overlay reads the same data: with no query, its empty state shows the same categories as a jump-to-area list.

Excluded automatically. Sections with layout = "browse" (the See-all page itself) and sections that contain zero workshops are skipped from the auto-detected category list, so you never see an empty heading or a self-reference.

Last Modified ·