Menu from Front Matter (+ multi-level)

I need to make a post navigation menu, and it is different for every single post, so I can`t just use regular Hugo menus.

I think that one approach for that is to use Front Matter in this way:

navigation:
- src: '#first'
  name: First 
- src: #second
  name: Second

And then just range it:

{{ range .Params.navigation }}
    <li>
        <a href="{{ .src }}">{{ .name }}</a>
    </li>
{{ end }}

But I don`t know how to make this menu multi-level.
I think I should set a parent in Front Matter, but how to properly display that parent-child relationship in layouts template?

    <li>
        <a>Top-level</a>        
    </li>
    <li>
        <a>Top-level, parent</a>
        <ul>
            <li>
                <a>Submenu, child</a>
            </li>
        </ul>
    </li>

Did you read https://gohugo.io/templates/menu-templates/? Should answer all your questions.

I did. But looks like {{ if .HasChildren }} / {{ range .Children }} doesn`t work with Front Matter menus.
Not sure or it is because it works only with .Site menus, or something else is on my way…

Front Matter:

navigation:
  basics:
    name: Basics
    src: '#basics'
  controls:
    name: controls
    src: '#controls'
    parent: basics

template:

{{ range .Params.navigation }}
    {{ if .HasChildren }} has children {{end}} /* no reaction */
{{end}}
1 Like

Is it even possible to create nested menu from Front Matter?

Both should work fine. You just need to define your menus in the front matter as in the docs linked above:

menu:
  docs:
    title: "how to use menus in templates"
    parent: "templates"
    weight: 130

In the above example from the docs, the page containing that front matter definition would have its own menu item as a child of the templates menu item. Assuming no other parent defined on the templates page, the templates page would then be the top level menu item of the docs menu, ie .Site.menus.docs.

Your example front matter does not seem to be defined as a menu, so menu-specific properties such as .HasChildren would not work: https://gohugo.io/variables/menus/#menu-entry-functions .

3 Likes

Thanks for the explanation, but it is still a bit confusing.
I have changed front matter to this state:

menu:
  templates:
    title: "templates"
  docs:
    title: "Documentation"
    parent: "templates"

Now when i do {{ range .Site.Menus.docs }} i can get the Docs menu item as a child, but the parent field is empty:

<ul class="top-level">              
    <li>
        <a href="#"></a>
    </li>
    <ul class="sub-menu">            
        <li>
        <a href="/blog/dota2-guide/">Documentation</a>
        </li>            
    </ul>   
</ul>

And if i do {{ range .Site.Menus.templates }} i get only Templates menu item as parent, with no children

Hi,

So, the templates parent here would be a different menu item (ie different page) whose identifier or title is template.

something like:

# content/lorem.md:
menu: 
  mymenu:
    title: "lorem"
    parent: "ipsum"
# content/ipsum.md:
menu: 
  mymenu:
    title: "ipsum"

.Site.Menus.mymenu would have ipsum as a top level menu item, and lorem as its child menu item.

Your above example, as you have found, puts that page in two menus: .Site.Menus.templates and .Site.Menus.docs.

You need to go to whichever page should be the parent menu item, and ‘attach’ it to the relevant menu.

2 Likes

Ok, now that’s make sense. But apparently I forgot to clarify that I need links leading to the same page.

This approach forces me to create other pages, while I only need to create links to anchor on same page(i.e. navigation inside of content of one particular post):

# content/longread.md

<nav>
    <a href="#part1"...
    <a href="#part2"...

<content>
    <h2 id="part1">
    <h1 id="part2">...

Oh, ok. Hmm, I’m not sure then that the front matter menu is the best approach.

Option 1: you could define everything in your config.toml file instead like here: https://gohugo.io/content-management/menus/#add-non-content-entries-to-a-menu

So something like (untested)

menu:
  main: 
    - name: longread
      url: "/longread/"
    - name: partone
      url: "/longread#part1"
      parent: longread
    - name: parttwo
      url: "/longread#part2"
      parent: longread

Or Option 2: you build custom navigation from .Params in front matter. Note that you would not have .HasChildren (and other menu-specific variables) available, so you would have to treat these as regular (ie non-menu) front matter.

I have generated my blog website using Hugo and created a multi-level menu structure.

I use tags to map my posts to submenus like this:

[[menu.main]]
    identifier = "java"
    name = "Java"
    url = "/java/"
    weight = 1
  [[menu.main]]
    identifier = "java-core"
    name = "Core Java"
    url = "/tags/java-core/"
    parent = "java"
    weight = 10
  [[menu.main]]
    identifier = "java-collection"
    name = "Collections"
    url = "/tags/java-collection/"
    parent = "java"
    weight = 20
  [[menu.main]]
    identifier = "java-design-pattern"
    name = "Design Patterns"
    url = "/tags/java-design-pattern/"
    parent = "java"
    weight = 30