Navigation Menus
EmDash menus are ordered lists of links that you manage through the admin interface. Menus support nesting for dropdowns and can link to pages, posts, taxonomy terms, or external URLs.
Querying Menus
Section titled “Querying Menus”Use getMenu() to fetch a menu by its unique name:
---import { getMenu } from "emdash";
const primaryMenu = await getMenu("primary");---
{primaryMenu && ( <nav> <ul> {primaryMenu.items.map(item => ( <li> <a href={item.url}>{item.label}</a> </li> ))} </ul> </nav>)}The function returns null if no menu exists with that name.
Menu Structure
Section titled “Menu Structure”A menu contains metadata and an array of items:
interface Menu { id: string; name: string; // Unique identifier ("primary", "footer") label: string; // Display name ("Primary Navigation") items: MenuItem[];}
interface MenuItem { id: string; label: string; url: string; // Resolved URL target?: string; // "_blank" for new window titleAttr?: string; // HTML title attribute cssClasses?: string; // Custom CSS classes children: MenuItem[]; // Nested items for dropdowns}URLs are resolved automatically based on the item type:
- Page/Post items resolve to
/{collection}/{slug} - Taxonomy items resolve to
/{taxonomy}/{slug} - Collection items resolve to
/{collection}/ - Custom links use the URL as-is
Rendering Nested Menus
Section titled “Rendering Nested Menus”Menu items can have children for dropdown navigation. Handle nesting by recursively rendering the children array:
---import { getMenu } from "emdash";import type { MenuItem } from "emdash";
interface Props { name: string;}
const menu = await getMenu(Astro.props.name);---
{menu && ( <nav class="nav"> <ul class="nav-list"> {menu.items.map(item => ( <li class:list={["nav-item", item.cssClasses]}> <a href={item.url} target={item.target} title={item.titleAttr} aria-current={Astro.url.pathname === item.url ? "page" : undefined} > {item.label} </a> {item.children.length > 0 && ( <ul class="submenu"> {item.children.map(child => ( <li> <a href={child.url} target={child.target}> {child.label} </a> </li> ))} </ul> )} </li> ))} </ul> </nav>)}Menu Item Types
Section titled “Menu Item Types”The admin supports five types of menu items:
| Type | Description | URL Resolution |
|---|---|---|
page | Link to a page | /{collection}/{slug} |
post | Link to a post | /{collection}/{slug} |
taxonomy | Link to a category or tag | /{taxonomy}/{slug} |
collection | Link to a collection archive | /{collection}/ |
custom | External or custom URL | Used as-is |
Listing All Menus
Section titled “Listing All Menus”Use getMenus() to retrieve all menu definitions (without items):
import { getMenus } from "emdash";
const menus = await getMenus();// Returns: [{ id, name, label }, ...]This is primarily useful for admin interfaces or debugging.
Creating Menus
Section titled “Creating Menus”Create menus through the admin interface at /_emdash/admin/menus, or use the admin API:
POST /_emdash/api/menusContent-Type: application/json
{ "name": "footer", "label": "Footer Navigation"}Add items to a menu:
POST /_emdash/api/menus/footer/itemsContent-Type: application/json
{ "type": "page", "referenceCollection": "pages", "referenceId": "page_privacy", "label": "Privacy Policy"}Add a custom external link:
POST /_emdash/api/menus/footer/itemsContent-Type: application/json
{ "type": "custom", "customUrl": "https://github.com/example", "label": "GitHub", "target": "_blank"}Reordering and Nesting
Section titled “Reordering and Nesting”Update item order and parent-child relationships with the reorder endpoint:
POST /_emdash/api/menus/primary/reorderContent-Type: application/json
{ "items": [ { "id": "item_1", "parentId": null, "sortOrder": 0 }, { "id": "item_2", "parentId": null, "sortOrder": 1 }, { "id": "item_3", "parentId": "item_2", "sortOrder": 0 } ]}This makes item_3 a child of item_2, creating a dropdown.
Complete Example
Section titled “Complete Example”The following example shows a responsive header with primary navigation:
---import { getMenu, getSiteSettings } from "emdash";
const settings = await getSiteSettings();const primaryMenu = await getMenu("primary");---
<html lang="en"> <head> <title>{settings.title}</title> </head> <body> <header class="header"> <a href="/" class="logo"> {settings.logo ? ( <img src={settings.logo.url} alt={settings.logo.alt || settings.title} /> ) : ( settings.title )} </a>
{primaryMenu && ( <nav class="main-nav" aria-label="Main navigation"> <ul> {primaryMenu.items.map(item => ( <li class:list={[item.cssClasses, { "has-children": item.children.length > 0 }]}> <a href={item.url} target={item.target} aria-current={Astro.url.pathname === item.url ? "page" : undefined} > {item.label} </a> {item.children.length > 0 && ( <ul class="dropdown"> {item.children.map(child => ( <li> <a href={child.url} target={child.target}>{child.label}</a> </li> ))} </ul> )} </li> ))} </ul> </nav> )} </header>
<main> <slot /> </main> </body></html>API Reference
Section titled “API Reference”getMenu(name)
Section titled “getMenu(name)”Fetch a menu by name with all items and resolved URLs.
Parameters:
name— The menu’s unique identifier (string)
Returns: Promise<Menu | null>
getMenus()
Section titled “getMenus()”List all menu definitions without items.
Returns: Promise<Array<{ id: string; name: string; label: string }>>