Menu System

On this page

This guide provides comprehensive documentation, for theme developers, on working with Aether's global menu system. You'll learn how to use both the built-in menu rendering and create custom menu implementations.

Overview

Aether provides a flexible global menu system that allows site administrators to manage navigation through the admin interface. Themes can render menus in two ways:

  1. Built-in HTML rendering - Using the {{html_menu}} variable
  2. Custom menu building - Using the {{menuItems}} array with STE's #each tag

Both approaches are fully supported and use the same underlying menu data managed by the GlobalMenuManager.

Using the Built-in Menu HTML

The simplest way to add a menu to your theme is to use the {{html_menu}} variable, which contains pre-rendered HTML for the site's menu.

Basic Usage

<header class="site-header">
    <div class="container">
        <a href="/" class="logo">{{site.siteTitle}}</a>
        {{html_menu}}
    </div>
</header>

This approach requires minimal effort as Aether handles the menu structure, nesting, and classes automatically through the GlobalMenuManager.generateMenuHtml() method.

HTML Structure and CSS Selectors

When using {{html_menu}}, Aether generates the following HTML structure:

<nav class="site-navigation">
    <ul class="nav-menu">
        <li id="menu-item-home" class="menu-item">
            <a href="/">Home</a>
        </li>
        <li id="menu-item-blog" class="menu-item">
            <a href="/blog">Blog</a>
        </li>
        <li id="menu-item-products" class="menu-item-has-children">
            <a href="/products">Products</a>
            <ul class="sub-menu">
                <li id="menu-item-product1" class="menu-item">
                    <a href="/products/product1">Product 1</a>
                </li>
                <li id="menu-item-product2" class="menu-item">
                    <a href="/products/product2">Product 2</a>
                </li>
            </ul>
        </li>
        <li id="menu-item-contact" class="menu-item custom-class">
            <a href="/contact" target="_blank" rel="noopener">Contact</a>
        </li>
    </ul>
</nav>

Key Selectors for Styling

Element Selector Description
Navigation wrapper .site-navigation The outermost container for the menu
Main menu .nav-menu The top-level menu list
Menu items .menu-item All menu items
Items with children .menu-item-has-children Menu items that contain submenus
Submenu .sub-menu Dropdown/nested menu lists
Menu item IDs #menu-item-{id} Each item has an ID based on its identifier
Custom classes .{custom-class} Additional classes set in the admin

Styling Examples

Here's a basic CSS example for styling the automatically generated menu:

/* Main navigation styles */
.site-navigation {
    background: #fff;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Top-level menu */
.nav-menu {
    display: flex;
    list-style: none;
    margin: 0;
    padding: 0;
}

/* All menu items */
.menu-item {
    position: relative;
}

.menu-item a {
    display: block;
    padding: 1rem;
    text-decoration: none;
    color: #333;
    transition: background-color 0.2s;
}

.menu-item a:hover {
    background: #f5f5f5;
}

/* Items with dropdown menus */
.menu-item-has-children > a::after {
    content: "â–ŧ";
    font-size: 0.7em;
    margin-left: 0.5em;
    vertical-align: middle;
}

/* Dropdown menus */
.sub-menu {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    background: #fff;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
    list-style: none;
    margin: 0;
    padding: 0;
    min-width: 200px;
    z-index: 100;
    border-radius: 4px;
}

.menu-item-has-children:hover > .sub-menu {
    display: block;
}

.sub-menu .menu-item {
    width: 100%;
}

.sub-menu .menu-item:first-child {
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
}

.sub-menu .menu-item:last-child {
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
}

JavaScript Enhancements

You can enhance the default menu with JavaScript for better mobile support:

document.addEventListener("DOMContentLoaded", function () {
    // Add mobile menu toggle
    const nav = document.querySelector(".site-navigation")
    const toggle = document.createElement("button")
    toggle.className = "menu-toggle"
    toggle.setAttribute("aria-label", "Toggle navigation menu")
    toggle.setAttribute("aria-expanded", "false")
    toggle.innerHTML = '<span class="menu-icon"></span>'
    nav.prepend(toggle)

    // Toggle menu on click
    toggle.addEventListener("click", function () {
        const expanded = this.getAttribute("aria-expanded") === "true"
        this.setAttribute("aria-expanded", !expanded)
        nav.classList.toggle("menu-open")
    })

    // Add accessibility support for submenus
    const subMenuParents = document.querySelectorAll(".menu-item-has-children > a")
    subMenuParents.forEach((item) => {
        item.setAttribute("aria-expanded", "false")
        item.setAttribute("aria-haspopup", "true")

        // Handle keyboard navigation
        item.addEventListener("keydown", function (e) {
            if (e.key === "Enter" || e.key === " ") {
                e.preventDefault()
                this.click()
            }
        })

        // Handle mobile tap behavior
        item.addEventListener("click", function (e) {
            if (window.innerWidth < 768) {
                e.preventDefault()
                const expanded = this.getAttribute("aria-expanded") === "true"
                this.setAttribute("aria-expanded", !expanded)
                this.parentNode.classList.toggle("submenu-open")
            }
        })
    })

    // Close menu when clicking outside
    document.addEventListener("click", function (e) {
        if (!nav.contains(e.target)) {
            nav.classList.remove("menu-open")
            toggle.setAttribute("aria-expanded", "false")
        }
    })
})

Building Custom Menus with Menu Items

For complete control over the menu structure and HTML, you can build custom menus using the {{menuItems}} array. This array contains enhanced menu items with hasChildren and depth properties.

Basic Custom Menu

<nav class="custom-navigation" role="navigation" aria-label="Main navigation">
    <ul class="menu">
        {{#each menuItems}}
        <!---->
        {{#not parent}}
        <li class="menu-link {{hasChildren ? `has-dropdown` : `no-children`}} {{class}}">
            <a
                href="{{url}}"
                target="{{target ? target : ``}}"
                rel="{{target ? `noopener` : ``}}"
                aria-haspopup="{{hasChildren ? `true` : ``}}"
                aria-expanded="{{hasChildren ? `false` : ``}}"
            >
                {{title}}
            </a>

            {{#if hasChildren}}
            <!---->
            {{#set parentID = id}}
            <ul class="dropdown" role="menu">
                {{#each menuItems}}
                <!---->
                {{#if parent == parentID}}
                <li class="dropdown-item" role="menuitem">
                    <a href="{{url}}" target="{{target ? target : ``}}" rel="{{target ? `noopener` : ``}}">{{title}}</a>
                </li>
                {{/if}}
                <!---->
                {{/each}}
            </ul>
            {{/if}}
        </li>
        {{/not}}
        <!---->
        {{/each}}
    </ul>
</nav>

Using STE Filters for Menu Enhancement

<nav class="enhanced-navigation">
    <ul class="main-menu">
        {{#set topLevelItems = menuItems | where("parent", null)}}
        <!---->
        {{#set sortedItems = topLevelItems | sortBy("order")}}
        <!---->
        {{#each sortedItems}}
        <li class="nav-item {{hasChildren ? `has-children` : `no-children`}} depth-{{depth}}">
            <a
                href="{{url | defaults('#')}}"
                class="nav-link {{class}}"
                target="{{target ? target : ``}}"
                rel="{{target ? `noopener` : ``}}"
            >
                {{title | capitalize}}
            </a>

            {{#if hasChildren}}
            <!---->
            {{#set childItems = menuItems | where("parent", id) | sortBy("order")}}
            <ul class="sub-menu">
                {{#each childItems}}
                <li class="sub-item">
                    <a href="{{url}}" target="{{target ? target : ``}}" rel="{{target ? `noopener` : ``}}">{{title}}</a>
                </li>
                {{/each}}
            </ul>
            {{/if}}
        </li>
        {{/each}}
    </ul>
</nav>

Menu Item Data Structure

The menuItems array contains objects with the following properties (enhanced by GlobalMenuManager.enhanceMenuItems()):

Property Type Description Example
id String Unique identifier for the menu item "home"
title String Display text for the menu item "Home"
url String Link URL "/"
order Number Position in the menu (lower numbers first) 1
parent String/null Parent menu item ID or null for top-level items "products"
target String/null Link target attribute (e.g., "_blank") "_blank"
class String/null Custom CSS class(es) "featured-link"
hasChildren Boolean Whether this item has children true
depth Number Nesting level (0 for top level) 0

Enhanced Menu Item Example

Here's what a complete menu item object looks like after enhancement:

{
  id: "products",
  title: "Products",
  url: "/products",
  order: 2,
  parent: null,
  target: null,
  class: "featured-menu",
  hasChildren: true,
  depth: 0
}
â„šī¸
Note: The flat structure doesn't include a children array. Child relationships are determined by the parent property.

Advanced Menu Techniques

Multi-level Menu Support

Create menus with multiple nesting levels using the parent-child relationships:

<!-- partials/menu-item.html -->
{{#set currentDepth = depth | defaults(0)}}
<li class="menu-item level-{{currentDepth}} {{hasChildren ? `has-children` : `no-children`}} {{class}}">
    <a
        href="{{url}}"
        target="{{target ? target : ``}}"
        rel="{{target ? `noopener` : ``}}"
        aria-haspopup="{{hasChildren ? `true` : ``}}"
    >
        {{title}}
    </a>

    {{#if hasChildren}}
    <!---->
    {{#set parentID = id}}
    <ul class="submenu depth-{{currentDepth}}">
        {{#each menuItems}} {{#if parent == parentID}} {{#include("partials/menu-item.html")}} {{/if}} {{/each}}
    </ul>
    {{/if}}
</li>

<!-- In your main template -->
<nav class="main-navigation">
    <ul class="primary-menu">
        {{#each menuItems}} {{#not parent}} {{#include("partials/menu-item.html")}} {{/not}} {{/each}}
    </ul>
</nav>

Implementing Active States

Add active state detection to highlight the current page:

<nav class="main-navigation">
    <ul class="primary-menu">
        {{#each menuItems}}
        <!---->
        {{#not parent}}
        <!---->
        {{#set isActive = url == customPath || (url == "/" && homeRoute)}}
        <li class="menu-item {{#if isActive}}active current-menu-item{{/if}} {{#if hasChildren}}has-children{{/if}}">
            <a href="{{url}}" aria-current="{{isActive ? `page` : ``}}">{{title}}</a>

            {{#if hasChildren}}
            <!---->
            {{#set parentID = id}}
            <ul class="submenu">
                {{#each menuItems}}
                <!---->
                {{#if parent == parentID}}
                <!---->
                {{#set isChildActive = url == customPath}}
                <li class="submenu-item {{#if isChildActive}}active{{/if}}">
                    <a href="{{url}}" aria-current="{{isChildActive ? `page` : ``}}">{{title}}</a>
                </li>
                {{/if}}
                <!---->
                {{/each}}
            </ul>
            {{/if}}
        </li>
        {{/not}}
        <!---->
        {{/each}}
    </ul>
</nav>

Filtering Menu Items

Create specific navigation sections by filtering menu items:

<!-- Main Navigation (exclude footer-only items) -->
<nav class="main-nav">
    <ul>
        {{#each menuItems}}
        <!---->
        {{#not parent}}
        <!---->
        {{#if class}}
        <!---->
        {{#not class | has("footer-only")}}
        <li><a href="{{url}}">{{title}}</a></li>
        {{/not}}
        <!---->
        {{#else}}
        <li><a href="{{url}}">{{title}}</a></li>
        {{/if}}
        <!---->
        {{/not}}
        <!---->
        {{/each}}
    </ul>
</nav>

<!-- Footer Navigation (only footer items) -->
<nav class="footer-nav">
    <ul>
        {{#each menuItems}}
        <!---->
        {{#not parent}}
        <!---->
        {{#if class && class | has("footer-only")}}
        <li><a href="{{url}}">{{title}}</a></li>
        {{/if}}
        <!---->
        {{/not}}
        <!---->
        {{/each}}
    </ul>
</nav>

Creating a Mega Menu

Implement a mega menu pattern by grouping child items:

<nav class="mega-menu">
    <ul class="primary-nav">
        {{#each menuItems}}
        <!---->
        {{#not parent}}
        <li class="nav-item {{#if hasChildren}}has-mega-menu{{/if}}">
            <a href="{{url}}">{{title}}</a>

            {{#if hasChildren}}
            <div class="mega-menu-wrapper">
                <div class="container">
                    <div class="mega-menu-grid">
                        {{#set childGroups = menuItems | where("parent", id) | groupBy("category")}}
                        <!---->
                        {{#each childGroups}}
                        <div class="mega-menu-column">
                            <h3>{{@key | defaults("Links")}}</h3>
                            <ul class="mega-menu-list">
                                {{#each this}}
                                <li><a href="{{url}}">{{title}}</a></li>
                                {{/each}}
                            </ul>
                        </div>
                        {{/each}}
                    </div>
                </div>
            </div>
            {{/if}}
        </li>
        {{/not}}
        <!---->
        {{/each}}
    </ul>
</nav>

Best Practices

1. Mobile Responsiveness

Ensure your menu works on all screen sizes:

@media (max-width: 768px) {
    .nav-menu {
        display: block;
    }
    .menu-item-has-children:hover > .sub-menu {
        display: none; /* Disable hover on mobile */
    }
    .menu-item-has-children.submenu-open > .sub-menu {
        display: block; /* Show on tap instead */
    }
}

2. Accessibility

Make your menu keyboard navigable and screen reader friendly:

<nav class="site-navigation" role="navigation" aria-label="Main Navigation">
    <ul class="nav-menu">
        {{#each menuItems}}
        <!---->
        {{#not parent}}
        <li class="menu-item {{#if hasChildren}}menu-item-has-children{{/if}}">
            <a
                href="{{url}}"
                aria-haspopup="{{hasChildren ? `true` : ``}}"
                aria-expanded="{{hasChildren ? `false` : ``}}"
                rel="{{target === `_blank` ? `noopener` : ``}}"
            >
                {{title}}
            </a>
        </li>
        {{/not}}
        <!---->
        {{/each}}
    </ul>
</nav>

3. Performance

Keep the menu HTML structure clean and optimized:

<!-- Filter to only show published menu items -->
{{#set visibleItems = menuItems | where("class", "published")}}
<!---->
{{#each visibleItems}}
<!---->
{{#if depth == 0}}
<!-- Only include top-level items here -->
{{/if}}
<!---->
{{/each}}

4. Flexibility

Design your menu system to work with any site structure:

<!-- Use conditional checks for special items -->
{{#each menuItems}}
<!---->
{{#not parent}}
<!---->
{{#if id == "home"}}
<li class="home-item">
    <a href="{{url}}">🏠 {{title}}</a>
</li>
{{#elseif url | has("/blog")}}
<li class="blog-item">
    <a href="{{url}}">📝 {{title}}</a>
</li>
{{#else}}
<li class="menu-item">
    <a href="{{url}}">{{title}}</a>
</li>
{{/if}}
<!---->
{{/not}}
<!---->
{{/each}}

5. Consistent Approach

Choose either built-in menu rendering or custom, not both in the same template.

Examples

Basic Responsive Menu

<!-- The HTML in your layout.html -->
<header class="site-header">
    <div class="container">
        <a href="/" class="site-logo">{{site.siteTitle}}</a>
        <button class="menu-toggle" aria-expanded="false" aria-label="Toggle navigation">
            <span class="sr-only">Toggle Menu</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
        </button>
        <nav class="main-navigation" role="navigation" aria-label="Main navigation">
            <ul class="menu">
                {{#each menuItems}}
                <!---->
                {{#not parent}}
                <li class="menu-item {{#if hasChildren}}has-submenu{{/if}} {{class}}">
                    <a href="{{url}}" target="{{target ? target : ``}}" rel="{{target ? `noopener` : ``}}">{{title}}</a>

                    {{#if hasChildren}}
                    <!---->
                    {{#set parentID = id}}
                    <button class="submenu-toggle" aria-expanded="false" aria-label="Toggle {{title}} submenu">
                        <span class="sr-only">Toggle Submenu</span>
                        <span class="arrow"></span>
                    </button>
                    <ul class="submenu">
                        {{#each menuItems}}
                        <!---->
                        {{#if parent = parentID}}
                        <li class="submenu-item {{class}}">
                            <a href="{{url}}" target="{{target ? target : ``}}" rel="{{target ? `noopener` : ``}}">
                                {{title}}
                            </a>
                        </li>
                        {{/if}}
                        <!---->
                        {{/each}}
                    </ul>
                    {{/if}}
                </li>
                {{/not}}
                <!---->
                {{/each}}
            </ul>
        </nav>
    </div>
</header>

<!-- Enhanced CSS with better mobile support -->
<style>
    .main-navigation {
        position: relative;
    }

    .menu {
        list-style: none;
        margin: 0;
        padding: 0;
        display: flex;
    }

    .menu-item {
        position: relative;
    }

    .menu-item a {
        display: block;
        padding: 1rem;
        text-decoration: none;
        color: #333;
        transition: color 0.2s ease;
    }

    .menu-item a:hover,
    .menu-item a:focus {
        color: #0066cc;
    }

    .has-submenu > a {
        padding-right: 2rem;
    }

    .submenu-toggle {
        position: absolute;
        top: 0.25rem;
        right: 0.25rem;
        width: 40px;
        height: 40px;
        background: transparent;
        border: none;
        cursor: pointer;
        display: none;
    }

    .submenu-toggle .arrow {
        display: block;
        width: 0;
        height: 0;
        border-left: 5px solid transparent;
        border-right: 5px solid transparent;
        border-top: 5px solid #333;
        margin: 0 auto;
        transition: transform 0.2s ease;
    }

    .submenu-toggle[aria-expanded="true"] .arrow {
        transform: rotate(180deg);
    }

    .submenu {
        position: absolute;
        top: 100%;
        left: 0;
        min-width: 200px;
        list-style: none;
        margin: 0;
        padding: 0;
        background: #fff;
        box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
        border-radius: 4px;
        display: none;
        z-index: 100;
    }

    .menu-item:hover > .submenu,
    .menu-item:focus-within > .submenu {
        display: block;
    }

    .submenu-item a {
        padding: 0.75rem 1rem;
        border-bottom: 1px solid #f0f0f0;
    }

    .submenu-item:last-child a {
        border-bottom: none;
    }

    /* Mobile Styles */
    .menu-toggle {
        display: none;
        background: transparent;
        border: none;
        cursor: pointer;
        padding: 10px;
    }

    .menu-toggle .icon-bar {
        display: block;
        width: 25px;
        height: 3px;
        background: #333;
        margin: 5px 0;
        transition: all 0.3s ease;
    }

    .sr-only {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }

    @media (max-width: 768px) {
        .menu-toggle {
            display: block;
        }

        .main-navigation {
            position: absolute;
            top: 100%;
            left: 0;
            right: 0;
            background: #fff;
            box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
            display: none;
        }

        .main-navigation.active {
            display: block;
        }

        .menu {
            flex-direction: column;
        }

        .submenu {
            position: static;
            box-shadow: none;
            background: #f9f9f9;
            border-radius: 0;
            padding-left: 1rem;
            display: none;
        }

        .submenu.active {
            display: block;
        }

        .menu-item:hover > .submenu,
        .menu-item:focus-within > .submenu {
            display: none;
        }

        .submenu-toggle {
            display: block;
        }
    }
</style>

<!-- Enhanced JavaScript with better accessibility -->
<script>
    document.addEventListener("DOMContentLoaded", function () {
        // Mobile menu toggle
        const menuToggle = document.querySelector(".menu-toggle")
        const mainNav = document.querySelector(".main-navigation")

        if (menuToggle && mainNav) {
            menuToggle.addEventListener("click", function () {
                const expanded = this.getAttribute("aria-expanded") === "true"
                this.setAttribute("aria-expanded", !expanded)
                mainNav.classList.toggle("active")

                // Update icon animation
                const iconBars = this.querySelectorAll(".icon-bar")
                iconBars.forEach((bar, index) => {
                    if (!expanded) {
                        if (index === 0) bar.style.transform = "rotate(45deg) translate(5px, 5px)"
                        if (index === 1) bar.style.opacity = "0"
                        if (index === 2) bar.style.transform = "rotate(-45deg) translate(7px, -6px)"
                    } else {
                        bar.style.transform = ""
                        bar.style.opacity = ""
                    }
                })
            })
        }

        // Submenu toggles for mobile
        const submenuToggles = document.querySelectorAll(".submenu-toggle")

        submenuToggles.forEach((toggle) => {
            toggle.addEventListener("click", function (e) {
                e.preventDefault()
                const expanded = this.getAttribute("aria-expanded") === "true"
                this.setAttribute("aria-expanded", !expanded)

                const submenu = this.nextElementSibling
                if (submenu) {
                    submenu.classList.toggle("active")
                }
            })
        })

        // Close menu when clicking outside
        document.addEventListener("click", function (e) {
            if (mainNav && !mainNav.contains(e.target) && !menuToggle.contains(e.target)) {
                mainNav.classList.remove("active")
                menuToggle.setAttribute("aria-expanded", "false")
            }
        })

        // Handle keyboard navigation
        const menuLinks = document.querySelectorAll(".menu-item a")
        menuLinks.forEach((link) => {
            link.addEventListener("keydown", function (e) {
                if (e.key === "Escape") {
                    this.blur()
                    if (mainNav.classList.contains("active")) {
                        mainNav.classList.remove("active")
                        menuToggle.setAttribute("aria-expanded", "false")
                        menuToggle.focus()
                    }
                }
            })
        })
    })
</script>

Social Media Menu

<!-- A special menu for social media links -->
<div class="social-menu">
    {{#each menuItems}}
    <!---->
    {{#if class && class | has("social")}}
    <a
        href="{{url}}"
        class="social-icon {{id}}"
        target="{{target ? target : ``}}"
        rel="{{target ? `noopener` : ``}}"
        aria-label="{{title}} (opens in new window)"
    >
        <span class="sr-only">{{title}}</span>
        {{#if id == "facebook"}}
        <!-- Facebook Icon -->
        {{#elseif id = "twitter"}}
        <!-- Twitter Icon -->
        {{#elseif id = "instagram"}}
        <!-- Instagram Icon -->
        {{#elseif id = "linkedin"}}
        <!-- LikedIn Icon -->
        {{#elseif id = "youtube"}}
        <!-- YouTube Icon -->
        {{#else}}
        <span class="icon-placeholder">{{title | capitalize}}</span>
        {{/if}}
    </a>
    {{/if}}
    <!---->
    {{/each}}
</div>

<style>
    .social-menu {
        display: flex;
        gap: 1rem;
        margin: 1rem 0;
        justify-content: center;
    }

    .social-icon {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 44px;
        height: 44px;
        border-radius: 50%;
        color: #fff;
        background: #333;
        transition: all 0.3s ease;
        text-decoration: none;
    }

    .social-icon:hover,
    .social-icon:focus {
        transform: translateY(-3px);
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
    }

    .social-icon svg {
        width: 24px;
        height: 24px;
    }

    .social-icon.facebook {
        background: #3b5998;
    }

    .social-icon.facebook:hover {
        background: #2d4373;
    }

    .social-icon.twitter {
        background: #1da1f2;
    }

    .social-icon.twitter:hover {
        background: #1a91da;
    }

    .social-icon.instagram {
        background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%);
    }

    .social-icon.instagram:hover {
        background: linear-gradient(45deg, #d6832b 0%, #c85a32 25%, #b91f39 50%, #a91c58 75%, #9a1579 100%);
    }

    .social-icon.linkedin {
        background: #0077b5;
    }

    .social-icon.linkedin:hover {
        background: #005885;
    }

    .social-icon.youtube {
        background: #ff0000;
    }

    .social-icon.youtube:hover {
        background: #cc0000;
    }

    .icon-placeholder {
        font-weight: bold;
        font-size: 1.2rem;
    }

    /* Responsive adjustments */
    @media (max-width: 480px) {
        .social-menu {
            gap: 0.5rem;
        }

        .social-icon {
            width: 40px;
            height: 40px;
        }

        .social-icon svg {
            width: 20px;
            height: 20px;
        }
    }
</style>

Breadcrumb Menu

Create a breadcrumb navigation using menu hierarchy:

<!-- Breadcrumb navigation based on current page -->
{{#if customPath || contentRoute}}
<nav class="breadcrumb-nav" aria-label="Breadcrumb">
    <ol class="breadcrumb">
        <li class="breadcrumb-item">
            <a href="/">Home</a>
        </li>

        {{#set currentUrl = customPath || "/post/" + contentId}}
        <!---->
        {{#each menuItems}}
        <!---->
        {{#set parentID = parent}}
        <!---->
        {{#if url == currentUrl}}
        <!---->
        {{#if parent}}
        <!---->
        {{#each menuItems}}
        <!---->
        {{#if parent == parentID}}
        <li class="breadcrumb-item">
            <a href="{{url}}">{{title}}</a>
        </li>
        {{/if}}
        <!---->
        {{/each}}
        <!---->
        {{/if}}
        <li class="breadcrumb-item active" aria-current="page">
            <span>{{title}}</span>
        </li>
        {{/if}}
        <!---->
        {{/each}}
    </ol>
</nav>
{{/if}}

Multi-language Menu Support

Handle multiple language menu items:

<nav class="multi-lang-menu">
    {{#set currentLang = site.language | defaults("en")}}

    <!-- Main menu in current language -->
    <ul class="primary-menu">
        {{#each menuItems}}
        <!---->
        {{#not parent}}
        <!---->
        {{#set itemLang = class | includes("lang-") ? (class | replace(".*lang-([a-z]{2,3}).*", "$1") | lowercase) :
        "en"}}
        <!---->
        {{#if itemLang == currentLang || itemLang == "en"}}
        <li class="menu-item {{class}}">
            <a href="{{url}}" {{target ? ` target="` + target + `" rel="noopener" ` : ``}}>{{title}}</a>
        </li>
        {{/if}}
        <!---->
        {{/not}}
        <!---->
        {{/each}}
    </ul>

    <!-- Language switcher -->
    <div class="language-switcher">
        {{#set languages = menuItems | where("class", "lang-switcher")}}
        <!---->
        {{#if languages | length > 0}}
        <select class="lang-select" aria-label="Choose language">
            {{#each languages}}
            <option value="{{url}}" {{class | has(currentLang) ? `selected` : ``}}>{{title}}</option>
            {{/each}}
        </select>
        {{/if}}
    </div>
</nav>

Sidebar Navigation Menu

Create a vertical sidebar menu for documentation or categories:

<aside class="sidebar-navigation">
    <nav class="docs-nav" role="navigation" aria-label="Documentation navigation">
        <h3 class="nav-title">Documentation</h3>

        {{#set docsItems = menuItems | where("class", "docs-menu")}}
        <!---->
        {{#if docsItems | length > 0}}
        <ul class="nav-list">
            {{#each docsItems}}
            <!---->
            {{#not parent}}
            <li class="nav-item {{#if hasChildren}}has-children{{/if}}">
                <a href="{{url}}" class="nav-link {{#if url == customPath}}active{{/if}}">{{title}}</a>

                {{#if hasChildren}}
                <!---->
                {{#set parentID = id}}
                <ul class="nav-sublist">
                    {{#each menuItems}}
                    <!---->
                    {{#if parent == parentID}}
                    <li class="nav-subitem">
                        <a href="{{url}}" class="nav-sublink {{#if url == customPath}}active{{/if}}">{{title}}</a>
                    </li>
                    {{/if}}
                    <!---->
                    {{/each}}
                </ul>
                {{/if}}
            </li>
            {{/not}}
            <!---->
            {{/each}}
        </ul>
        {{#else}}
        <p class="no-nav">No documentation menu items found.</p>
        {{/if}}
    </nav>
</aside>

<style>
    .sidebar-navigation {
        width: 250px;
        background: #f8f9fa;
        padding: 1.5rem;
        border-radius: 8px;
        position: sticky;
        top: 2rem;
    }

    .nav-title {
        margin: 0 0 1rem 0;
        font-size: 1.1rem;
        color: #333;
        border-bottom: 2px solid #e9ecef;
        padding-bottom: 0.5rem;
    }

    .nav-list {
        list-style: none;
        margin: 0;
        padding: 0;
    }

    .nav-item {
        margin-bottom: 0.5rem;
    }

    .nav-link {
        display: block;
        padding: 0.5rem 0.75rem;
        color: #495057;
        text-decoration: none;
        border-radius: 4px;
        transition: all 0.2s ease;
    }

    .nav-link:hover,
    .nav-link:focus {
        background: #e9ecef;
        color: #212529;
    }

    .nav-link.active {
        background: #007bff;
        color: white;
    }

    .nav-sublist {
        list-style: none;
        margin: 0.5rem 0 0 0;
        padding: 0;
        padding-left: 1rem;
        border-left: 2px solid #dee2e6;
    }

    .nav-subitem {
        margin-bottom: 0.25rem;
    }

    .nav-sublink {
        display: block;
        padding: 0.25rem 0.5rem;
        color: #6c757d;
        text-decoration: none;
        font-size: 0.9rem;
        border-radius: 3px;
        transition: all 0.2s ease;
    }

    .nav-sublink:hover,
    .nav-sublink:focus {
        background: #e9ecef;
        color: #495057;
    }

    .nav-sublink.active {
        background: #28a745;
        color: white;
    }

    .no-nav {
        color: #6c757d;
        font-style: italic;
        margin: 0;
    }

    @media (max-width: 768px) {
        .sidebar-navigation {
            width: 100%;
            position: static;
            margin-bottom: 2rem;
        }
    }
</style>

Advanced Menu Management

Menu API Integration

Create dynamic menu management in your theme:

<!-- Admin-only menu management interface -->
{{#if editable && currentUser.role == "admin"}}
<div class="menu-admin-panel">
    <h3>Menu Management</h3>
    <button class="btn-add-menu-item" data-action="add-menu-item">Add Menu Item</button>

    <div class="menu-items-list">
        {{#each menuItems}}
        <div class="menu-item-admin" data-item-id="{{id}}">
            <div class="item-details">
                <strong>{{title}}</strong>
                <span class="item-url">{{url}}</span>
                {{#if parent}}
                <span class="item-parent">Child of: {{parent}}</span>
                {{/if}}
            </div>
            <div class="item-actions">
                <button class="btn-edit" data-action="edit" data-id="{{id}}">Edit</button>
                <button class="btn-delete" data-action="delete" data-id="{{id}}">Delete</button>
            </div>
        </div>
        {{/each}}
    </div>
</div>

<script>
    document.addEventListener("DOMContentLoaded", function () {
        // Menu item management
        const adminPanel = document.querySelector(".menu-admin-panel")

        if (adminPanel) {
            adminPanel.addEventListener("click", function (e) {
                const action = e.target.dataset.action
                const itemId = e.target.dataset.id

                switch (action) {
                    case "add-menu-item":
                        addMenuItem()
                        break
                    case "edit":
                        editMenuItem(itemId)
                        break
                    case "delete":
                        deleteMenuItem(itemId)
                        break
                }
            })
        }

        async function addMenuItem() {
            const title = prompt("Menu item title:")
            const url = prompt("Menu item URL:")

            if (title && url) {
                try {
                    const response = await fetch("/api/menu", {
                        method: "POST",
                        headers: { "Content-Type": "application/json" },
                        body: JSON.stringify({
                            id: title.toLowerCase().replace(/\s+/g, "-"),
                            title: title,
                            url: url,
                            order: Date.now(), // Simple ordering
                        }),
                    })

                    if (response.ok) {
                        location.reload()
                    }
                } catch (error) {
                    console.error("Error adding menu item:", error)
                }
            }
        }

        async function editMenuItem(itemId) {
            // Implementation for editing menu items
            console.log("Edit menu item:", itemId)
        }

        async function deleteMenuItem(itemId) {
            if (confirm("Delete this menu item?")) {
                try {
                    const response = await fetch(`/api/menu/${itemId}`, {
                        method: "DELETE",
                    })

                    if (response.ok) {
                        location.reload()
                    }
                } catch (error) {
                    console.error("Error deleting menu item:", error)
                }
            }
        }
    })
</script>
{{/if}}

Conclusion

This comprehensive guide provides theme developers with everything needed to implement sophisticated navigation systems using Aether's global menu system. Whether you choose the simple {{html_menu}} approach or build custom menus with the {{menuItems}} array, you have the flexibility to create responsive, accessible, and visually appealing navigation that integrates seamlessly with Aether's content management capabilities.

Key takeaways:

  • Use {{html_menu}} for quick implementation with automatic hierarchy support
  • Use {{menuItems}} for complete control over HTML structure and styling
  • Leverage STE filters like where, sortBy, and has for advanced menu manipulation
  • Always include accessibility features like ARIA labels and keyboard navigation
  • Design for mobile-first with responsive navigation patterns
  • Take advantage of the enhanced menu data with hasChildren and depth properties

The global menu system's flat storage structure with parent-child relationships provides maximum flexibility while the enhanced menu items give you all the data needed to create sophisticated navigation experiences.