Build a Navigation Menu From Jekyll Data

Hard-coding the same navigation in several layouts makes small changes unnecessarily annoying. Jekyll data files give the links one home, while an include handles the HTML.

Create _data/nav.yml with the menu structure:

- title: "Home"
  href: "/"

- title: "About"
  href: "/about/"

- title: "Projects"
  href: "/projects/"
  subcategories:
    - subtitle: "Project1"
      subhref: "#"
    - subtitle: "Project2"
      subhref: "#"

The filename matters. A file named nav.yml becomes available as site.data.nav in Liquid.

Next, create _includes/navigation.html:

<nav aria-label="Primary">
  <ul>
    {% for nav in site.data.nav %}
      {% if nav.subcategories != null %}
        <li>
          <a href="{{ site.url }}{{ nav.href }}">{{ nav.title }} ▼</a>
          <ul>
            {% for subcategory in nav.subcategories %}
              <li>
                <a href="{{ site.url }}{{ subcategory.subhref }}">
                  {{ subcategory.subtitle }}
                </a>
              </li>
            {% endfor %}
          </ul>
        </li>
      {% elsif nav.title == page.title %}
        <li class="active">
          <a href="{{ nav.href }}">{{ nav.title }}</a>
        </li>
      {% else %}
        <li>
          <a href="{{ site.url }}{{ nav.href }}">{{ nav.title }}</a>
        </li>
      {% endif %}
    {% endfor %}
  </ul>
</nav>

The loop renders an ordinary link unless the item contains subcategories, in which case it adds a nested list. It also gives the matching page an active class for styling.

Include the menu wherever the layout needs it:

{% include navigation.html %}

For a small site, this is enough. If navigation grows more complex, I would keep the data file but move state detection away from title matching—titles can change, while a dedicated identifier is much less surprising.