Menu System
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:
- Built-in HTML rendering - Using the
{{html_menu}}
variable - 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
}
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
, andhas
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
anddepth
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.