Confusion regarding proper use of menus

I have a site where I want to be able to visually display to the user indication about what page they are on by adding an active class to the navigation element. I have this working (see my site), but it looks like a terrible hack. Surely there is a better way of doing this?

In my config.toml, I declare my site-wide menus as following:

[menu]
    [[menu.main]]
    identifier = "news"
    name       = "News"
    url        = "/"
    weight     = 0

    [[menu.main]]
    identifier = "projects"
    name       = "Projects"
    url        = "/projects/"
    weight     = 1

    [[menu.main]]
    identifier = "portfolio"
    name       = "Portfolio"
    url        = "/portfolio/"
    weight     = 2

    [[menu.main]]
    identifier = "research"
    name       = "Research"
    url        = "/research/"
    weight     = 3

    [[menu.main]]
    identifier = "about"
    name       = "About"
    url        = "/about/"
    weight     = 4

Then in my Boostrap navbar partial I’m doing this to determine if the current menu item should be marked as active:

            <div class="navbar-collapse collapse" id="navbar">
                <ul class="nav navbar-nav navbar-right">
                    {{ range .Site.Menus.main }}
<li
{{ if $.IsNode }}{{ if eq .Identifier "news" }}class="active"{{ end }}{{ end }}
{{ if $.IsPage }}{{ if eq .Identifier $.Type }}class="active"{{ end }}{{ end }}
>
                        <a href="{{ .URL }}">{{ .Name }}</a>
                    </li>
                    {{ end }}
                </ul>
            </div>

Do I really have to treat nodes and pages differently like this? Is there any way I can check one single variable against the current menu to determine if it should be highlighted? I wouldn’t mind setting front matter on the pages, but there is no way to do this for list.html based templates…

1 Like

Yes, the menu system was very confusing for me as well when starting with Hugo. I have done stuff to make it a little easier, but it still is a brain-grinder, and the docs “could be better”

A couple of tips:

  • Node menu items are configured in config.toml (or similar), page items in front matter
  • Both node and page have IsMenuCurrent and HasMenuCurrent so you can set them active by using the same template, e.g.:
{{ $currentNode := . }}
{{ range .Site.Menus.main }}
<a class="sidebar-nav-item{{if or ($currentNode.IsMenuCurrent "main" .) ($currentNode.HasMenuCurrent "main" .) }} active{{end}}" href="{{.URL}}">{{ .Name }}</a>
{{ end }}

In the example above I have only one level, so I mark it active “if it is either is on the menu page itself or a page that is a child of that menu”.

  • Defining menus in all the content pages just so you get the “active” marker is too much work, so I added the
SectionPagesMenu = "main"

With this, you get menu items for every section with the pages connected to it. If this is “good enough”, then you’ll only have to define menu items for the “home page”, “about page” etc.

3 Likes

Thank you for your quick and helpful response! I was surprised to see that those functions indeed work on nodes as in the documentation it is stated:

Additionally, there are some relevant functions available on the page:
IsMenuCurrent (menu string, menuEntry *MenuEntry ) bool
HasMenuCurrent (menu string, menuEntry *MenuEntry) bool

This lead me to believe that this was a no-starter. Everything works properly now. For those searching this question later, my code was changed as follows:

            <div class="navbar-collapse collapse" id="navbar">
                <ul class="nav navbar-nav navbar-right">
                    {{ range .Site.Menus.main }}
                    <li {{ if or ($.IsMenuCurrent "main" .) ($.HasMenuCurrent "main" .) }} class="active"{{ end }}>
                        <a href="{{ .URL }}">{{ .Name }}</a>
                    </li>
                    {{ end }}
                </ul>
            </div>

And my config is now includes in it:

SectionPagesMenu = "main"

Thanks again!

I created an issue to track this:

Pull requests for doc updates are always welcome!

Small followup: is it possible to make this work for taxonomies as well? For example when I am viewing /tags/foo, it would be nice to have the Tags menu item displayed as active. It currently only highlights when I am accessing the tag listing:

Contents of config.toml:

baseurl          = "http://foosoft.net/"
disableRss       = true
disableSitemap   = true
languageCode     = "en-us"
sectionPagesMenu = "main"
title            = "FooSoft Productions"

[taxonomies]
tag = "tags"

[menu]
    [[menu.main]]
    identifier = "news"
    name       = "News"
    url        = "/"
    weight     = 0

    [[menu.main]]
    identifier = "projects"
    name       = "Projects"
    url        = "/projects/"
    weight     = 1

    [[menu.main]]
    identifier = "portfolio"
    name       = "Portfolio"
    url        = "/portfolio/"
    weight     = 2

    [[menu.main]]
    identifier = "research"
    name       = "Research"
    url        = "/research/"
    weight     = 3

    [[menu.main]]
    identifier = "about"
    name       = "About"
    url        = "/about/"
    weight     = 4

    [[menu.main]]
    identifier = "tags"
    name       = "Tags"
    url        = "/tags/"
    weight     = 5

layouts/_default/terms.html

{{ partial "header.html" . }}
{{ partial "navbar.html" . }}
<div class="container">
    <h1>{{ title $.Data.Plural }}</h1>
    <ul>
        {{ range $key, $value := .Data.Terms.ByCount }}
        <li><a href="/{{ $.Data.Plural }}/{{ $value.Name | urlize }}">{{ $value.Name }}</a> ({{ $value.Count }})</li>
        {{ end }}
    </ul>
</div>
{{ partial "footer.html" . }}

I added the “Section menus for the lazy blogger” to fit my specific need. Taxonomies wasn’t on that list.

So you would either have to define the taxonomy menu items in all the pages (a hassle) - or do some special string matching for the taxonomy pages.

I’m looking through the Hugo source now to see if I can do something about this. It looks like taxonomy pages never make it into the Site.Pages array, making them kind of special from everything else.

Nodes are “special”. This discussion talks about making them “less special”:

I tried the suggestions this discussion brought up but I am still having trouble properly utilizing the menus for pages.
My config.toml is:

baseurl = "www.site.com"
languageCode = "en-us"
title = "siteName"
[params]
author = "Name"
description = "description"	
canonifyurls = true

The front matter of one of my pages is:

  +++
  title = "What is CompanyName?"
  description = ""
  tags = [ ]
  categories = [ ]
  type = "page"
 url = "/what-is-companyName/"
[menu.main]
name = "What is companyName?"
weight = 0  url = "/what-is-companyName/"
 +++

I then used this code to test if it worked

 {{ range .Site.Menus.main }}
<li class="nav-item"><span class="nav-item-separator">//</span><a href="{{ .URL }}">{{ .Name }}</a></li>
{{ end }}

On the index page it would work but when I followed the link to the page the navigation would no longer show up. I’ve tried using different versions of the code from themes I have seen but it still won’t work. Is there some step I am missing? I don’t know Go so I am not sure what I may be missing or misunderstanding. Iwould really appreciate any help

First off, I love Hugo - it is pretty much everything I want in a Static Site Generator!

One issue I was having was getting the menu to add the class="active" on pages with a custom type (think Contact Us page).

Here is the “hack” I have come up with that works for me:

##Setup

Define the menu in config.toml (nice for management of the menu):

baseurl             = "https://<url>/"
languageCode        = "en-us"
title               = "<title>"
theme               = "<theme>"
sectionPagesMenu    = "main"

[menu]
    [[menu.main]]
        name = "Home"
        identifier = "home"
        url = "/"
        weight = 10

    [[menu.main]]
        name = "Blog"
        identifier = "blog"
        url = "/blog/"
        weight = 20

    [[menu.main]]
        name = "Contact"
        identifier = "contact-us"
        url = "/contact-us/"
        weight = 30

Then I have two files for Contact Us: contact-us.md under content and contact-us/index.html under layouts.

#Content

contact-us.md contains (note the same [menu.main] identifier from config.toml):

+++
date = "2016-06-26T11:41:12-07:00"
draft = false

title = "Contact Us"
description = "Blah. Blah."
type = "contact-us"
layout = "index"
[menu.main]
    identifier = "contact-us"

+++

##Layout

contact-us/index.html contains all my template code ({{ partial "header-page.html" . }}) and regular HTML.

My menu rendering code is:

{{ $currentNode := . }}
{{ range .Site.Menus.main }}
    <li class="{{if or ($currentNode.IsMenuCurrent "main" .) ($currentNode.HasMenuCurrent "main" .) }}active{{end}}">
        <a href="{{ .URL }}">{{ .Name }}</a>
        {{ if .HasChildren }}
            <ul class="sub-menu">
                {{ range .Children }}
                    <li class="{{if $currentNode.HasMenuCurrent "main" . }}active{{ end }}">
                        <a href="{{ .URL }}">{{ .Name }}</a>
                    </li>
                {{ end }}
            </ul>
        {{ end }}
    </li>
{{ end }}

##Note

Just a heads up: You will have hugo complain about duplicate menu identifiers:

ERROR: 2016/12/08 14:15:25 site.go:1449: Two or more menu items have the same name/identifier in Menu "main": "contact-us".

but I have noticed zero side effects.

Hopefully this helps someone.

##Edit
On further review: My original issue only seems to be an issue at all because I don’t follow “convention” on file management. My Contact Us content is located at: content/contact-us.md. The menu system works as expected when you locate your content at the following path: content/contact-us/index.md. I just don’t like having a bunch of folders with a single index.md in each of them.

Why do you have two menu items with the same identifier? (the side effect is that only one of the will “survive”, so if you don’t see any side-effects, you can safely delete one of them)

When I delete the [menu.main] identifier from contact-us.md then class="active" doesn’t get added when you are on the Contact Us page.

The [[menu.main]] definition with the weight, url, etc. in the config.toml is nice to have because then I manage pretty much everything in one place (otherwise I am sure I could move the [[menu.main]] entry to the contact-us.md file).

##Edit:
If you have a better way of associating a [[menu.main]] entry in the config.toml with content then please let me know! I might be doing something wrong - I am probably doing something wrong. :stuck_out_tongue_closed_eyes:

On further review: My original issue only seems to be an issue at all because I don’t follow “convention” on file management. My Contact Us content is located at: content/contact-us.md. The menu system works as expected when you locate your content at the following path: content/contact-us/index.md. I just don’t like having a bunch of folders with a single index.md in each of them.

Hi bryanjeal, I just wanted to share that I found your very last point (On further review:) very helpful in debugging this issue myself. I was experiencing the same issue as you and was unaware of the convention of adding content as an index.md within the relevant directory.

This was not obvious after watching the Giraffe Academy video on this page: http://gohugo.io/templates/single-page-templates/.

Anyway it’s all working so just wanted to extend my thanks for thinking of those of us who would visit this page in search of a solution. Cheers!

I modify a little of FooSoft’s code, and this works for me now.

In my config.toml, I set the value of identifier the same as section name.
For example, I have files in /content/note/, the section name is note, so I set the note identifier as note too.

My config.toml:

[[menu.main]]
    identifier = "post"
    name = "Post"
    url = "/"
    weight = 1

[[menu.main]]
    identifier = "note"
    name = "Note"
    url = "/note/"
    weight = 2

[[menu.main]]
    identifier = "tags"
    name = "Tag"
    url = "/tags/"
    weight = 2

$.Type always get <section> of /<section>/... except for home page. So the code below will works.

My navigation.html:

    <div class="navbar-nav-scroll">
        <ul class="navbar-nav bd-navbar-nav flex-row">
        {{ range .Site.Menus.main }}
            {{ if eq $.Kind "home" }}
                {{ $.Scratch.Set "active" ($.IsMenuCurrent "main" .) }}
            {{ else }}
                {{ $.Scratch.Set "active" (eq .Identifier $.Type) }}
            {{ end }}
            {{ $active := $.Scratch.Get "active" }}
            <li class="nav-item {{ if $active }}active{{end}}">
                <a class="nav-link" href="{{ .URL | absLangURL  }}">{{ .Pre  }}<span>{{ .Name  }}</span></a>
            </li>
        {{ end  }}
        </ul>
    </div>

My website: https://memoex.github.io
You can find the code here: https://github.com/memoex/blue/blob/master/layouts/partials/nav.html

1 Like

Hi this thread is very informative and has helped me to understand.
What do I need to do to create the menu in a sidebar?
What’s missing in the documentation is a complete example from a-z of how to make a menu, there’s only pieces of the puzzle you have to work out how to put them all together and it’s all very error pron

2 Likes

Thanks!

And if your menu has entries referring to content pages, remember to define them in config.toml with pageref instead of url and then using .IsMenuCurrent (see .IsMenuCurrent returns false when menu configured via config file vs. front matter · Issue #9150 · gohugoio/hugo · GitHub). My current solution building on top of yours:

<nav class="menu">
	<button class="menu__btn" aria-haspopup="true" aria-expanded="false" tabindex="0">
		<span class="menu__btn-title" tabindex="-1">{{ T "menu_label" }}</span>
	</button>
	<ul class="menu__list">
		{{- $currentNode := . }}
		{{- range sort .Site.Menus.main.ByWeight }}

		{{ if eq $.Kind "home" }}
			{{ $.Scratch.Set "active" ($.IsMenuCurrent "main" .) }}
		{{ else }}
			{{ $.Scratch.Set "active" (or (eq .Identifier $.Type) ($currentNode.IsMenuCurrent "main" .)) }}
		{{ end }}
		{{ $active := $.Scratch.Get "active" }}

		<li class="menu__item{{ if $active }} menu__item--active{{ end }}">
			<a class="menu__link" href="{{ .URL }}">
				{{ .Pre }}
				<span class="menu__text">{{ .Name }}</span>
				{{ .Post }}
			</a>
		</li>
		{{- end }}
	</ul>
</nav>