Add a Navbar

‘Minima’, the default Jekyll theme, uses the list header_pages in _config.yml (site.header_pages) to render its navbar:

header_pages:
  - blog.html
  - about.md

This works for a small set of links, but starts to bother when for large content driven sites.

In the absense of the header_pages list, Minima lists all site pages found under the root folder in the header navigation bar.
See _includes\header.html

Modifying this is a non-trivial exercise, which explains the proliferation of Jekyll themes.
My search led me to Feeling Responsive as the closest to what I had in mind.
I might switch over yet, but meanwhile it’s tinker time…

Target

two-level navigation menu.

First create the _data\navbar.yml

navbar:
  - title: Personal
    page: personal/index.md
    subfolderitems:
      - page: Blog
        url: /personal/blog.html
      - page: DMS Memorial
        url: /personal/dms-memorial.html
  - title: Technology
    subfolderitems:
      - page: Architecture
        url: /technology/architecture.html
      - page: Web Development
        url: /technology/web-development.html
  - title: About
    url: /about/

Onto the navbar

The Liquid template under the example produces an unordered list – useful for confirmation…


{% for item in site.data.samplelist.toc %}
    <h3>{{ item.title }}</h3>
      <ul>
        {% for entry in item.subfolderitems %}
          <li><a href="{{ entry.url }}">{{ entry.page }}</a></li>
        {% endfor %}
      </ul>
  {% endfor %}

But you need to stitch the CSS classes for navbar display.

Going with Bootswatch Cerulean, an open-source collection of themes built over Bootstrap.


_includes\head.html

Copy it over from the Minima gem location (bundle info --path minima), to the project _includes\ folder.

  • Import the Navbar Cerulean CSS classes and the Bootstrap Javascript for the drop down menu (defer skips the need to put the script before the closing </body> tag (in the footer).
  <link href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.8/cerulean/bootstrap.min.css" rel="stylesheet">
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous" defer></script>

_includes\header.html

Replace the copy in the project _includes with the content below:

Bootstrap has a responsive .container class that provides a classic desktop width on widescreen monitors inplace of Minima’s constrained .wrapper central column. Replacing .wrapper in both header.html and _layouts/default.html alllows for a comfortable width:


<main class="page-content" aria-label="Content">  
   <div class="container">  
     {{ content }}  
   </div>  
</main>  

The sticky-top class in <header> tag keeps the header from scrolling off the screen.


<header class="site-header sticky-top" role="banner"> <!-- 'sticky-top' keeps navbar locked to the top of browser viewport -->
  <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
    
     <!-- Bootstrap .container-fluid: stretches navbar to 100% width, pushing links to outer edges.  
     Minima .wrapper constrains width to a central column.  
     .container class gives a nice middle-path desktop width -->
    <div class="container d-flex align-items-center justify-content-between w-100">

      <a class="navbar-brand" rel="author" href="{{ '/' | relative_url }}">
        {{ site.title | escape }}
      </a>
      
      <!-- Mobile Hamburger Toggle -->
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      
      <!-- Links Wrapper -->
      <div class="collapse navbar-collapse justify-content-end" id="navbarNavDropdown">
        <ul class="navbar-nav">
          
          {% for item in site.data.navbar.navbar %}
            {% if item.subfolderitems and item.subfolderitems.size > 0 %}
              
              {% assign dropdown_active = false %}
              {% for entry in item.subfolderitems %}
                {% if entry.url == page.url %}
                  {% assign dropdown_active = true %}
                {% endif %}
              {% endfor %}
              
              <li class="nav-item dropdown">
                <a class="nav-link dropdown-toggle {% if dropdown_active %}active{% endif %}" 
                   href="#" 
                   id="navbarDropdown{{ forloop.index }}" 
                   role="button" 
                   data-bs-toggle="dropdown" 
                   aria-expanded="false"
                   {% if dropdown_active %}aria-current="page"{% endif %}>
                  {{ item.title }}
                </a>
                <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown{{ forloop.index }}">
                  {% for entry in item.subfolderitems %}
                    <li>
                      <a class="dropdown-item {% if entry.url == page.url %}active{% endif %}" href="{{ entry.url | relative_url }}">
                        {{ entry.page }}
                      </a>
                    </li>
                  {% endfor %}
                </ul>
              </li>

            {% else %}
              
              <li class="nav-item">
                <a class="nav-link {% if item.url == page.url %}active{% endif %}" href="{{ item.url | relative_url }}" {% if item.url == page.url %}aria-current="page"{% endif %}>
                  {{ item.title }}
                </a>
              </li>

            {% endif %}
          {% endfor %}

        </ul>
      </div>

    </div>
  </nav>
</header>