Support one level of dropdown menus in the navbar

A `main` menu entry with child entries (others with `parent:` set) is
now rendered as a Bootstrap dropdown; flat entries are unchanged.
Active-state highlighting is trailing-slash tolerant and marks the
dropdown parent active when one of its children is the current page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 17:26:39 +02:00
co-authored by Claude Sonnet 5
parent ec9cba0555
commit 071ee344a3
2 changed files with 56 additions and 20 deletions
+31
View File
@@ -34,6 +34,37 @@ Optional params: `title`, `height` (a CSS length such as `60vh`, which
crops the image with `object-fit: cover`) and `class`. crops the image with `object-fit: cover`) and `class`.
## Navigation
The navbar renders the `main` menu. A top-level entry that has child
entries (other entries pointing at it with `parent:`) is rendered as a
Bootstrap dropdown; entries without children stay as plain links. Only
one level of nesting is supported (Bootstrap 5 has no nested submenus).
```yaml
# hugo.yaml / menus.yaml
menu:
main:
- name: Home
pageRef: /
weight: 1
- name: Research
weight: 2
- name: Proposals
parent: Research
pageRef: /proposals
weight: 1
- name: History
parent: Research
pageRef: /history
weight: 2
```
The entry matching the current page (and its parent, for a dropdown
child) gets Bootstrap's `active` class. A parent entry acts only as the
dropdown toggle, so it does not need its own `url`/`pageRef`.
## Custom error page ## Custom error page
The theme has a custom 404 error page. The theme has a custom 404 error page.
+21 -16
View File
@@ -14,35 +14,40 @@
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
{{ range .Site.Menus.main }} {{- /* Current page, without a trailing slash, for menu-item highlighting. */ -}}
{{ if .HasChildren }} {{- $current := strings.TrimSuffix "/" .RelPermalink -}}
<!-- Dropdown menu for items with children --> {{- range .Site.Menus.main -}}
{{- $isActive := and .URL (eq (strings.TrimSuffix "/" .URL) $current) -}}
{{- if .HasChildren -}}
{{- /* A menu entry with sub-entries (set `parent:` on them) becomes a dropdown. */ -}}
{{- range .Children -}}
{{- if and .URL (eq (strings.TrimSuffix "/" .URL) $current) }}{{ $isActive = true }}{{ end -}}
{{- end -}}
{{- $id := printf "navbar-%s" (.Identifier | default (.Name | urlize)) -}}
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbar{{ .Name | urlize }}" <a class="nav-link dropdown-toggle{{ if $isActive }} active{{ end }}" href="#"
role="button" data-bs-toggle="dropdown" aria-expanded="false"> id="{{ $id }}" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ .Name }} {{ .Name }}
</a> </a>
<ul class="dropdown-menu" aria-labelledby="navbar{{ .Name | urlize }}"> <ul class="dropdown-menu" aria-labelledby="{{ $id }}">
{{ range .Children }} {{- range .Children }}
<li> <li>
<a class="dropdown-item {{ if eq .URL ($.RelPermalink) }}active{{ end }}" <a class="dropdown-item{{ if and .URL (eq (strings.TrimSuffix "/" .URL) $current) }} active{{ end }}"
href="{{ .URL | relURL }}"> href="{{ .URL }}">
{{ .Name }} {{ .Name }}
</a> </a>
</li> </li>
{{ end }} {{- end }}
</ul> </ul>
</li> </li>
{{ else }} {{- else -}}
<!-- Regular menu item -->
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {{ if eq .URL ($.RelPermalink) }}active{{ end }}" <a class="nav-link{{ if $isActive }} active{{ end }}" href="{{ .URL }}">
href="{{ .URL | relURL }}">
{{ .Name }} {{ .Name }}
</a> </a>
</li> </li>
{{ end }} {{- end -}}
{{ end }} {{- end }}
</ul> </ul>
</div> </div>
</div> </div>