Excluding page from Jekyll navigation bar

It is possible to create a multi-level, context-sensitive navigation like you described without plugins, I have done it.

The only caveat is that you need to maintain a YAML data file with your menu hierarchy - with my approach, it's not possible to generate this automatically from your directory structure.

I'll show the short version here, but I have a way more detailed explanation on my blog:

  • Building a pseudo-dynamic tree menu with Jekyll
  • Example project on GitHub

1. Create a YAML data file (/_data/menu.yml) which contains your menu hierarchy:

- text: Home
  url: /
- text: First menu
  url: /first-menu/
  subitems:
    - text: First menu (sub)
      url: /first-menu/first-menu-sub/
      subitems:
        - text: First menu (sub-sub)
          url: /first-menu/first-menu-sub/first-menu-sub-sub/
- text: Second menu
  url: /second-menu/
  subitems:
    - text: Second menu (sub)
      url: /second-menu/second-menu-sub/

2. Create an include file (/_includes/nav.html) with the following content:

{% assign navurl = page.url | remove: 'index.html' %}
<ul>
{% for item in include.nav %}
    <li>
        <a href="{{ item.url }}">
            {% if item.url == navurl %}
                <b>{{ item.text }}</b>
            {% else %}
                {{ item.text }}
            {% endif %}
        </a>
    </li>
    {% if item.subitems and navurl contains item.url %}
        {% include nav.html nav=item.subitems %}
    {% endif %}
{% endfor %}
</ul>

This include file will take care of showing the correct navigation for each page:

  • showing the next level of subitems only for the current page
  • displaying the current page in bold

If you don't understand what exactly the include file is doing under the covers, read my blog post - I explained it there, in great detail (in the section "The recursive include file").


3. In your main layout file, embed the include file:

{% include nav.html nav=site.data.menu %}

This will display the navigation there.
Note that I'm passing the complete data file from step 1 to the include.


That's all!

As I said in the beginning:

The only disadvantage of this approach is that each time you create a new page, you also need to insert the page's title and URL into the data file.

But on the other hand, this makes it very easy to exclude some pages from the navigation: you just don't add them to the data file.


I personally do ;

Front matter for page that appears in main menu

---
layout: default
title: Home
menu: main
weight: 10
---

Main menu template (classes are from twitter bootstrap) :

<ul class="nav navbar-nav">
  {% comment %}Jekyll can now sort on custom page key{% endcomment %}
  {% assign pages = site.pages | sort: 'weight' %} 
  {% for p in pages %}
    {% if p.menu == 'main' %}
      <li{% if p.url == page.url %} class="active"{% endif %}>
         <a href="{{ site.baseurl }}{{ p.url }}">{{ p.title }}</a>
      </li>
    {% endif %}
  {% endfor %}
</ul>

You can then replicate that at any level by setting a custom var in the yaml front matter :

menu : foo

and passing a value to a menu template

{% include navbar.html  menuLevel="foo" %}

And intercept it like this :

{% if p.menu == menuLevel %}

Any page that doesn't expose a menu: toto will not appear in navigation.


If you are coming from the basic Jekyll theme, the simplest way to exclude pages from the header site navigation is to add an unless page.exclude exception.

(page.exclude is a new Yaml frontmatter attribute.)

By default, this is in _includes/header.html:

{% for page in site.pages %}
    {% unless page.exclude %}
       {% if page.title %}
         <a class="page-link" href="{{ page.url | prepend: site.baseurl }}">{{ page.title }}</a>
       {% endif %}
    {% endunless %}
{% endfor %}

and a corresponding tag to the Yaml frontmatter of any page:

---
... other attributes ...
exclude: true
---

Credit to Michael Chadwick.

Tags:

Liquid

Jekyll