Skip to documentation content

Menu (multi-level)

All complete examples

Demonstrate Menu (multi-level).

What this example teaches

  • Reuse the Menu (multi-level) theme partial across pages
  • Keep custom fields on the template that owns them

View this pattern on the live demo site

Files

Nested header navigation used by the site chrome.

Liquid
{% comment %}
	Purpose: Renders a responsive multi-level navigation (Bootstrap-style navbar with dropdowns). Supports up to three levels when max_menu_depth is 3 (top bar, first dropdown, nested submenu).

	Escaping: Class inputs used inside the item loop are normalized once via `var ... | escape` above. Other class strings use `default | escape` at their single output.

	Inputs:
		menu - Menu object from {% menu %} (iterable; items use name, href.value, items for children).
		max_menu_depth - Maximum depth to render: 2 (top + one dropdown) or 3 (nested submenu under a second-level item). Clamped to 2–3. Defaults to 2.
		nav_classes - Classes on the outer <nav>. Default: minimal navbar + expand breakpoint.
		nav_id - Optional id on <nav>.
		aria_label - Accessible name for the navigation region. Defaults to 'Main'.
		show_navbar_toggler - When true, outputs toggler and collapse wrapper. Defaults to true.
		mobile_menu_label - Label beside the toggler on small screens. Defaults to 'Menu'.
		mobile_menu_label_classes - Classes on the mobile label span. Defaults to 'navbar-text d-lg-none'.
		container_classes - Inner wrapper. Defaults to 'container'.
		ul_classes - Root <ul> classes. Defaults to 'navbar-nav'.
		nav_link_classes - Top-level leaf links when show_navbar_toggler is false, or desktop leaf links when true.
		nav_link_classes_mobile - Top-level leaf links on small screens when show_navbar_toggler is true.
		dropdown_toggle_classes - Top-level <a> that opens a dropdown.
		dropdown_menu_classes - First-level dropdown <ul>.
		dropdown_item_classes - Second-level links that are leaves (no third level or max_menu_depth is 2).
		nav_item_leaf_mobile_classes - <li> for top-level leaves on small screens when toggler is used.
		nav_item_leaf_desktop_classes - <li> for top-level leaves on large screens when toggler is used.
		nav_item_dropdown_classes - <li> wrapping a top-level dropdown.
		nested_submenu_li_classes - <li> wrapping a second-level item that has a nested (third-level) menu. Defaults to 'dropend' (Bootstrap nested dropdown).
		nested_dropdown_toggle_classes - <a> for a second-level parent with children (opens third level). Defaults to 'dropdown-item dropdown-toggle'.
		nested_dropdown_menu_classes - <ul> for third-level items. Defaults to 'dropdown-menu'.
		nested_dropdown_item_classes - Links at third level. Defaults to 'dropdown-item'.
		navbar_toggler_classes - Toggler button classes. Defaults to 'navbar-toggler'.
		navbar_toggler_aria_label - aria-label on toggler. Defaults to 'Toggle navigation'.
		collapse_classes - Collapse wrapper classes. Defaults to 'collapse navbar-collapse'.
		trailing_markup - Optional markup after the root <ul> (still inside collapse when toggler is true).
{% endcomment -%}
{%- if menu.is_valid -%}
	{%- var max_menu_depth = max_menu_depth | default: 2 | to_int | at_least: 2 | at_most: 3 -%}
	{%- var show_navbar_toggler = show_navbar_toggler | default: true -%}
	{%- var nav_link_classes = nav_link_classes | default: 'nav-link' | escape -%}
	{%- var nav_link_classes_mobile = nav_link_classes_mobile | default: 'nav-link' | escape -%}
	{%- var dropdown_toggle_classes = dropdown_toggle_classes | default: 'nav-link dropdown-toggle' | escape -%}
	{%- var dropdown_menu_classes = dropdown_menu_classes | default: 'dropdown-menu' | escape -%}
	{%- var dropdown_item_classes = dropdown_item_classes | default: 'dropdown-item' | escape -%}
	{%- var nav_item_leaf_mobile_classes = nav_item_leaf_mobile_classes | default: 'nav-item d-lg-none' | escape -%}
	{%- var nav_item_leaf_desktop_classes = nav_item_leaf_desktop_classes | default: 'nav-item d-none d-lg-block' | escape -%}
	{%- var nav_item_dropdown_classes = nav_item_dropdown_classes | default: 'nav-item dropdown' | escape -%}
	{%- var nested_submenu_li_classes = nested_submenu_li_classes | default: 'dropend' | escape -%}
	{%- var nested_dropdown_toggle_classes = nested_dropdown_toggle_classes | default: 'dropdown-item dropdown-toggle' | escape -%}
	{%- var nested_dropdown_menu_classes = nested_dropdown_menu_classes | default: 'dropdown-menu' | escape -%}
	{%- var nested_dropdown_item_classes = nested_dropdown_item_classes | default: 'dropdown-item' | escape -%}
	<nav class="{{ nav_classes | default: 'navbar navbar-expand-lg' | escape }}"{% if nav_id is_valid %} id="{{ nav_id }}"{% endif %} aria-label="{{ aria_label | default: 'Main' | escape }}">
		<div class="{{ container_classes | default: 'container' | escape }}">
			{%- if show_navbar_toggler -%}
				{%- id collapse_id = suffix: '_collapse' -%}
				<span class="{{ mobile_menu_label_classes | default: 'navbar-text d-lg-none' | escape }}">{{ mobile_menu_label | default: 'Menu' }}</span>
				<button class="{{ navbar_toggler_classes | default: 'navbar-toggler' | escape }}" type="button" data-bs-toggle="collapse" data-bs-target="#{{ collapse_id }}" aria-controls="{{ collapse_id }}" aria-expanded="false" aria-label="{{ navbar_toggler_aria_label | default: 'Toggle navigation' | escape }}">
					<span class="navbar-toggler-icon"></span>
				</button>
				<div class="{{ collapse_classes | default: 'collapse navbar-collapse' | escape }}" id="{{ collapse_id }}">
			{%- endif -%}
			<ul class="{{ ul_classes | default: 'navbar-nav' | escape }}{% unless show_navbar_toggler %} w-100{% endunless %}">
				{%- for menu_item in menu -%}
					{%- if menu_item.items.size > 0 -%}
						<li class="{{ nav_item_dropdown_classes }}">
							<a class="{{ dropdown_toggle_classes }}" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
								{{- menu_item.name -}}
							</a>
							<ul class="{{ dropdown_menu_classes }}">
								{%- for sub in menu_item.items -%}
									{%- if max_menu_depth >= 3 and sub.items.size > 0 -%}
										<li class="{{ nested_submenu_li_classes }}">
											<a class="{{ nested_dropdown_toggle_classes }}" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">{{ sub.name }}</a>
											<ul class="{{ nested_dropdown_menu_classes }}">
												{%- for grand in sub.items -%}
													<li><a class="{{ nested_dropdown_item_classes }}" href="{{ grand.href.value }}">{{ grand.name }}</a></li>
												{%- endfor -%}
											</ul>
										</li>
									{%- else -%}
										<li><a class="{{ dropdown_item_classes }}" href="{{ sub.href.value }}">{{ sub.name }}</a></li>
									{%- endif -%}
								{%- endfor -%}
							</ul>
						</li>
					{%- elsif show_navbar_toggler -%}
						<li class="{{ nav_item_leaf_mobile_classes }}">
							<a class="{{ nav_link_classes_mobile }}" href="{{ menu_item.href.value }}">{{ menu_item.name }}</a>
						</li>
						<li class="{{ nav_item_leaf_desktop_classes }}">
							<a class="{{ nav_link_classes }}" href="{{ menu_item.href.value }}">{{ menu_item.name }}</a>
						</li>
					{%- else -%}
						<li class="nav-item">
							<a class="{{ nav_link_classes }}" href="{{ menu_item.href.value }}">{{ menu_item.name }}</a>
						</li>
					{%- endif -%}
				{%- endfor -%}
			</ul>
			{%- if trailing_markup is_valid -%}
				{{ trailing_markup }}
			{%- endif -%}
			{%- if show_navbar_toggler -%}
				</div>
			{%- endif -%}
		</div>
	</nav>
{%- endif -%}