Skip to content

Navigation

Nimbu provides a flexible menu system for building navigation across your site. Create menus in the admin, then render them with the {% nav %} tag or build custom navigation with full control.

Menus in Nimbu:

  • Are created and managed in the Nimbu admin
  • Support unlimited nesting levels
  • Can link to pages, products, collections, external URLs, or custom content
  • Automatically track active states
  • Support multiple menus per site (main, footer, mobile, etc.)

Creating Menus

In the Nimbu admin:

  1. Go to Settings → Menus
  2. Create a new menu (e.g., "main", "footer", "mobile")
  3. Add menu items by selecting pages, products, or entering custom URLs
  4. Drag items to organize hierarchy and order
  5. Save the menu

The {% nav %} Tag

The {% nav %} tag automatically renders complete menu markup with active states:

Basic Usage

liquid
<nav>
  {% nav main %}
</nav>

This renders a simple unordered list with links.

With Options

liquid
{% nav main,
  class: "nav-menu",
  link_class: "nav-link",
  item_class: "nav-item",
  active_item_class: "active",
  active_parent_class: "active-parent",
  depth: 2
%}

Complete Example

liquid
<header class="main-header">
  <div class="container">
    <a href="/" class="logo">{{ site.name }}</a>

    <nav class="main-nav">
      {% nav main,
        class: "menu",
        link_class: "menu-link",
        item_class: "menu-item",
        submenu_class: "submenu",
        active_item_class: "active",
        depth: 3
      %}
    </nav>
  </div>
</header>

Tag Parameters

ParameterDescriptionDefault
classCSS class for <ul>nil
link_classCSS class for <a> tagsnil
item_classCSS class for <li> tagsnil
submenu_classCSS class for nested <ul>nil
submenu_wrapper_classWrapper class for submenusnil
active_item_classClass for active menu itemsactive
active_parent_classClass for active parent itemsactive-parent
depthMaximum nesting depth to rendernil (all)
idID attribute for main <ul>nil
excludeComma-separated IDs of items to excludenil
no_wrapperSkip outer <ul> wrapperfalse

Manual Menu Building

For complete control, iterate through menu items manually:

liquid
<nav class="main-nav">
  <ul class="menu">
    {% for item in menus.main.items %}
      <li class="menu-item {% if item.active? %}active{% endif %}">
        <a href="{{ item.target }}">{{ item.name }}</a>

        {% if item.children? %}
          <ul class="submenu">
            {% for subitem in item.children %}
              <li class="submenu-item {% if subitem.active? %}active{% endif %}">
                <a href="{{ subitem.target }}">{{ subitem.name }}</a>

                {% if subitem.children? %}
                  <ul class="subsubmenu">
                    {% for subsubitem in subitem.children %}
                      <li class="{% if subsubitem.active? %}active{% endif %}">
                        <a href="{{ subsubitem.target }}">{{ subsubitem.name }}</a>
                      </li>
                    {% endfor %}
                  </ul>
                {% endif %}
              </li>
            {% endfor %}
          </ul>
        {% endif %}
      </li>
    {% endfor %}
  </ul>
</nav>
PropertyDescription
nameDisplay name of the menu item
targetURL the item links to
kindType: "page", "product", "collection", "url", "channel"
descriptionOptional description text
childrenArray of child menu items
children?Boolean: has child items
leaf?Boolean: has no children
active?Boolean: item matches current page
active_parent?Boolean: has active child

Real-World Navigation Example

Complete header navigation from a production theme:

liquid
<header class="main-header sticky">
  <div class="container">
    <a href="{% if locale_url_prefix %}{{ locale_url_prefix }}{% else %}/{% endif %}" class="logo">
      <img class="normal" src="{{ 'logo.png' | theme_image_url }}" alt="{{ site.name }}">
      <img class="scroll" src="{{ 'logo-white.png' | theme_image_url }}" alt="{{ site.name }}">
    </a>

    <!-- Desktop Navigation -->
    <nav id="main-nav" class="desktop-nav">
      {% cache 'main-menu', expires_in: 3600 %}
        {% nav main, class: "nav", link_class: "nav-link" %}
      {% endcache %}
    </nav>

    <!-- Language Switcher -->
    {% assign translation_count = page.translations | size %}
    {% if translation_count > 1 %}
      <div class="language-switcher">
        {% for translation in page.translations %}
          <a href="{% localized_path translation.locale %}"
             class="lang-link {% if locale == translation.locale %}active{% endif %}">
            {{ translation.locale | upcase }}
          </a>
        {% endfor %}
      </div>
    {% elsif config.locales.size > 1 %}
      <div class="language-switcher">
        {% for locale_option in config.locales %}
          <a href="{% localized_path locale_option[1] %}"
             class="lang-link {% if locale == locale_option[1] %}active{% endif %}">
            {{ locale_option[1] | upcase }}
          </a>
        {% endfor %}
      </div>
    {% endif %}

    <!-- Mobile Menu Toggle -->
    <button class="menu-toggle" aria-label="Toggle menu">
      <span class="hamburger-box">
        <span class="hamburger-inner"></span>
      </span>
    </button>
  </div>

  <!-- Mobile Navigation -->
  <nav id="mobile-nav" class="mobile-nav">
    {% nav main, class: "mobile-menu", link_class: "mobile-link" %}
  </nav>
</header>

Use the {% breadcrumbs %} tag for automatic breadcrumb generation:

liquid
<nav class="breadcrumbs" aria-label="Breadcrumb">
  {% breadcrumbs %}
</nav>

Custom Breadcrumbs

Build custom breadcrumbs for more control:

liquid
<nav class="breadcrumbs">
  <a href="/">Home</a>

  {% if template == 'product' %}
    <span class="separator">/</span>
    <a href="/shop">Shop</a>

    {% if product.product_type_name %}
      <span class="separator">/</span>
      <a href="/shop/{{ product.product_type_name | parameterize }}">
        {{ product.product_type_name }}
      </a>
    {% endif %}

    <span class="separator">/</span>
    <span class="current">{{ product.name }}</span>

  {% elsif template == 'article' %}
    <span class="separator">/</span>
    <a href="{{ article.blog.url }}">{{ article.blog.name }}</a>
    <span class="separator">/</span>
    <span class="current">{{ article.title }}</span>

  {% elsif template == 'page' %}
    <span class="separator">/</span>
    <span class="current">{{ page.title }}</span>
  {% endif %}
</nav>

Structured Data Breadcrumbs

Add schema.org markup for SEO:

liquid
<nav aria-label="Breadcrumb">
  <ol class="breadcrumbs" itemscope itemtype="http://schema.org/BreadcrumbList">
    <li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">
      <a href="/" itemprop="item">
        <span itemprop="name">Home</span>
      </a>
      <meta itemprop="position" content="1">
    </li>

    {% if product %}
      <li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">
        <a href="/shop" itemprop="item">
          <span itemprop="name">Shop</span>
        </a>
        <meta itemprop="position" content="2">
      </li>

      <li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">
        <span itemprop="name">{{ product.name }}</span>
        <meta itemprop="position" content="3">
      </li>
    {% endif %}
  </ol>
</nav>

Language / Locale Switching

Using Page Translations

Best for content pages:

liquid
{% if page.translations.size > 1 %}
  <div class="language-switcher">
    {% for translation in page.translations %}
      <a href="{{ translation.url }}"
         class="{% if locale == translation.locale %}active{% endif %}"
         hreflang="{{ translation.locale }}">
        {{ translation.localized_language }}
      </a>
    {% endfor %}
  </div>
{% endif %}

Using localized_path

For consistent language switching across all pages:

liquid
<div class="language-switcher">
  {% for locale_option in config.locales %}
    <a href="{% localized_path locale_option[1] %}"
       class="{% if locale == locale_option[1] %}active{% endif %}">
      {{ locale_option[1] | upcase }}
    </a>
  {% endfor %}
</div>

With Language Names

Display full language names:

liquid
<div class="language-switcher">
  {% for translation in page.translations %}
    <a href="{{ translation.url }}"
       class="lang-option {% if locale == translation.locale %}active{% endif %}">
      <span class="lang-code">{{ translation.locale | upcase }}</span>
      <span class="lang-name">{{ translation.localized_language }}</span>
    </a>
  {% endfor %}
</div>
liquid
<div class="language-dropdown">
  <button class="current-language">
    {{ locale | upcase }}
    <svg class="icon-chevron"><use xlink:href="#chevron-down"></use></svg>
  </button>

  <ul class="language-options">
    {% for translation in page.translations %}
      {% unless locale == translation.locale %}
        <li>
          <a href="{{ translation.url }}" hreflang="{{ translation.locale }}">
            {{ translation.localized_language }}
          </a>
        </li>
      {% endunless %}
    {% endfor %}
  </ul>
</div>

Mobile Navigation Patterns

Hamburger Menu

liquid
<button class="menu-toggle" aria-label="Toggle menu" aria-expanded="false">
  <span class="sr-only">Menu</span>
  <span class="hamburger">
    <span></span>
    <span></span>
    <span></span>
  </span>
</button>

<nav class="mobile-nav" aria-hidden="true">
  <div class="mobile-nav-inner">
    {% nav main, class: "mobile-menu" %}
  </div>
</nav>

<script>
const toggle = document.querySelector('.menu-toggle');
const nav = document.querySelector('.mobile-nav');

toggle.addEventListener('click', function() {
  const expanded = this.getAttribute('aria-expanded') === 'true';
  this.setAttribute('aria-expanded', !expanded);
  nav.setAttribute('aria-hidden', expanded);
  document.body.classList.toggle('nav-open');
});
</script>

Accordion Submenus

liquid
<nav class="mobile-nav">
  <ul class="mobile-menu">
    {% for item in menus.main.items %}
      <li class="mobile-menu-item">
        {% if item.children? %}
          <button class="submenu-toggle" aria-expanded="false">
            {{ item.name }}
            <svg class="icon-chevron"><use xlink:href="#chevron-down"></use></svg>
          </button>
          <ul class="mobile-submenu" hidden>
            {% for subitem in item.children %}
              <li><a href="{{ subitem.target }}">{{ subitem.name }}</a></li>
            {% endfor %}
          </ul>
        {% else %}
          <a href="{{ item.target }}">{{ item.name }}</a>
        {% endif %}
      </li>
    {% endfor %}
  </ul>
</nav>

Mega Menu

Build a mega menu with columns:

liquid
<nav class="main-nav">
  <ul class="menu">
    {% for item in menus.main.items %}
      <li class="menu-item {% if item.children? %}has-megamenu{% endif %}">
        <a href="{{ item.target }}">{{ item.name }}</a>

        {% if item.children? %}
          <div class="megamenu">
            <div class="container">
              <div class="megamenu-columns">
                {% for subitem in item.children %}
                  <div class="megamenu-column">
                    <h3><a href="{{ subitem.target }}">{{ subitem.name }}</a></h3>
                    {% if subitem.children? %}
                      <ul>
                        {% for subsubitem in subitem.children %}
                          <li><a href="{{ subsubitem.target }}">{{ subsubitem.name }}</a></li>
                        {% endfor %}
                      </ul>
                    {% endif %}
                  </div>
                {% endfor %}
              </div>
            </div>
          </div>
        {% endif %}
      </li>
    {% endfor %}
  </ul>
</nav>
liquid
<footer class="main-footer">
  <div class="container">
    <div class="footer-columns">
      <div class="footer-column">
        <h4>{% translate 'footer.company', default: 'Company' %}</h4>
        <ul class="footer-menu">
          {% for item in menus.footer.items %}
            <li><a href="{{ item.target }}">{{ item.name }}</a></li>
          {% endfor %}
        </ul>
      </div>

      <div class="footer-column">
        <h4>{% translate 'footer.help', default: 'Help' %}</h4>
        <ul class="footer-menu">
          {% for item in menus.help.items %}
            <li><a href="{{ item.target }}">{{ item.name }}</a></li>
          {% endfor %}
        </ul>
      </div>
    </div>
  </div>
</footer>

Accessibility Best Practices

Skip Navigation

liquid
<a href="#main-content" class="skip-link">
  Skip to main content
</a>

<nav aria-label="Main navigation">
  {% nav main %}
</nav>

<main id="main-content" tabindex="-1">
  {{ content_for_body }}
</main>

ARIA Labels

liquid
<nav aria-label="Main navigation">
  {% nav main %}
</nav>

<nav aria-label="Footer navigation">
  {% nav footer %}
</nav>

<nav aria-label="Breadcrumb">
  {% breadcrumbs %}
</nav>

Current Page Indication

liquid
{% for item in menus.main.items %}
  <li>
    <a href="{{ item.target }}"
       {% if item.active? %}aria-current="page"{% endif %}>
      {{ item.name }}
    </a>
  </li>
{% endfor %}

Performance Optimization

Cache Menus

liquid
{% cache 'main-menu', expires_in: 3600 %}
  {% nav main, class: "nav" %}
{% endcache %}

Defer Mobile Menu JavaScript

liquid
<script defer src="{{ 'mobile-menu.js' | asset_url }}"></script>

Troubleshooting

  • Check menu exists with correct slug in Nimbu admin
  • Verify menus.menu_slug or {% nav menu_slug %} matches admin slug
  • Check for Liquid syntax errors

Active States Not Working

  • Ensure URLs in menu match actual page URLs
  • Check that active_item_class is set if using custom CSS
  • Verify current page URL is correct
  • Check depth parameter isn't limiting nesting
  • Verify child items exist in admin
  • Ensure template supports multi-level menus

Next Steps

Build flexible, accessible navigation for your Nimbu site!