To generate a section menu you need to recursively “walk” a tree of things:
- Menu entries, or
- The file system, or
- Sections (directories with _index.md files)
Creating menu entries for a section menu is (a) time consuming, (b) error prone, and (c) a hassle to maintain—you have to create another entry each time you create a new page.
Walking the file system with os.ReadFile and os.ReadDir is limited to the physical file system. These template functions cannot read Hugo’s virtual file system, which means you cannot mount content from an external source. Additionally, setting the active class
and the correct aria-current
value requires some messy comparisons, and you have to use .GetPage
for every file and directory to get the .RelPermalink
and .LinkTitle
values.
Which leaves us with the third and best option: walk the sections. For this to work as desired, every directory must have an _index.md file. To avoid creating links to specific directories (text only), we’ll add a front matter flag to specific _index.md files.
content structure
content/
├── folder-1/
│ ├── file-1-1.md
│ ├── file-1-2.md
│ └── _index.md
├── folder-2/
│ └── _index.md
├── folder-3/
│ ├── folder-3-1/
│ │ ├── file-3-1-1.md
│ │ ├── file-3-1-2.md
│ │ └── _index.md
│ ├── file-3-1.md
│ ├── file-3-2.md
│ └── _index.md
└── _index.md
layouts/partials/menus/section.html
{{- /*
Renders a section menu.
@param {page} page The current page.
@param {pages} pages The collection of top-level pages to walk.
@returns {template.HTML}
@example {{- partial "menus/section.html" (dict "page" . "pages" site.Sections) }}
*/}}
<nav class="menu-section">
<ul>
{{- partial "section-menu/walk.html" . }}
</ul>
</nav>
{{- define "partials/section-menu/walk.html" }}
{{- range .pages }}
<li>
{{- if .Params.sectionMenuTextOnly }}
{{ .LinkTitle }}
{{- else }}
{{- if .Eq $.page }}
<a class="active" aria-current="page" href="{{ .RelPermalink }}">{{ .LinkTitle }}</a>
{{- else if in $.page.Ancestors . }}
<a class="ancestor" aria-current="true" href="{{ .RelPermalink }}">{{ .LinkTitle }}</a>
{{- else }}
<a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a>
{{- end }}
{{- end }}
{{- with .Pages }}
<ul>
{{- partial "section-menu/walk.html" (dict "page" $.page "pages" .) }}
</ul>
{{- end }}
</li>
{{- end }}
{{- end }}
Then render the menu with something like:
<aside>
{{ partial "menus/section.html" (dict "page" . "pages" site.Sections) }}
</aside>
To show text only (no link) for a particular page, add a front matter parameter:
+++
title = 'Folder 1'
date = 2023-05-05T13:04:46-07:00
draft = false
weight = 100
sectionMenuTextOnly = true
+++
Try it:
git clone --single-branch -b hugo-forum-topic-44216 https://github.com/jmooring/hugo-testing hugo-forum-topic-44216
cd hugo-forum-topic-44216
hugo server
Active menu items are yellow, while ancestor items are cyan.