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.
Menu System Overview
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:
- Go to Settings → Menus
- Create a new menu (e.g., "main", "footer", "mobile")
- Add menu items by selecting pages, products, or entering custom URLs
- Drag items to organize hierarchy and order
- Save the menu
The {% nav %} Tag
The {% nav %} tag automatically renders complete menu markup with active states:
Basic Usage
<nav>
{% nav main %}
</nav>This renders a simple unordered list with links.
With Options
{% 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
<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
| Parameter | Description | Default |
|---|---|---|
class | CSS class for <ul> | nil |
link_class | CSS class for <a> tags | nil |
item_class | CSS class for <li> tags | nil |
submenu_class | CSS class for nested <ul> | nil |
submenu_wrapper_class | Wrapper class for submenus | nil |
active_item_class | Class for active menu items | active |
active_parent_class | Class for active parent items | active-parent |
depth | Maximum nesting depth to render | nil (all) |
id | ID attribute for main <ul> | nil |
exclude | Comma-separated IDs of items to exclude | nil |
no_wrapper | Skip outer <ul> wrapper | false |
Manual Menu Building
For complete control, iterate through menu items manually:
<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>Menu Item Properties
| Property | Description |
|---|---|
name | Display name of the menu item |
target | URL the item links to |
kind | Type: "page", "product", "collection", "url", "channel" |
description | Optional description text |
children | Array 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:
<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>Breadcrumbs
Use the {% breadcrumbs %} tag for automatic breadcrumb generation:
<nav class="breadcrumbs" aria-label="Breadcrumb">
{% breadcrumbs %}
</nav>Custom Breadcrumbs
Build custom breadcrumbs for more control:
<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:
<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:
{% 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:
<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:
<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>Dropdown Language Selector
<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
<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
<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:
<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>Footer Navigation
<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
<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
<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
{% 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
{% cache 'main-menu', expires_in: 3600 %}
{% nav main, class: "nav" %}
{% endcache %}Defer Mobile Menu JavaScript
<script defer src="{{ 'mobile-menu.js' | asset_url }}"></script>Troubleshooting
Menu Not Showing
- Check menu exists with correct slug in Nimbu admin
- Verify
menus.menu_slugor{% 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_classis set if using custom CSS - Verify current page URL is correct
Submenu Not Rendering
- Check
depthparameter isn't limiting nesting - Verify child items exist in admin
- Ensure template supports multi-level menus
Next Steps
- Pages - Add navigation to page templates
- Layouts - Include navigation in layouts
- Multilingual - Translate menus and navigation
- Global Variables - Menu drop reference
Build flexible, accessible navigation for your Nimbu site!