Skip to main content

Navigation Menu

Overview

Use the Navigation Menu family when you need authored navigation items with optional dropdown panels, keyboard navigation, animated viewport transitions, and a mobile drawer helper that automatically derives its structure from the desktop menu. The desktop family is composed of six components that nest together, plus a separate mobile helper and an explicit mobile-only node for complex mega-menu content.

The root component manages open state, keyboard navigation, and optional animated viewport behavior. Navigation Menu Mobile is a separate component placed elsewhere on the page — it connects to the desktop menu by a shared menuId and builds a drawer-based mobile experience at runtime.

Authoring Structure

The desktop menu follows a strict nesting hierarchy:

Navigation Menu (desktop root)
└── Navigation Menu List
├── Navigation Menu Item
│ ├── plain link
│ ├── OR Navigation Menu Trigger + Navigation Menu Content
│ │ └── optional Navigation Menu Mobile Item subtree inside content
│ └── OR link + Navigation Menu Trigger + Navigation Menu Content
└── Navigation Menu Item ...

Navigation Menu Mobile (separate — anywhere on the page)

Desktop and Mobile Are Separate

Navigation Menu Mobile is not nested inside Navigation Menu. It is placed as a sibling element elsewhere on the page (typically inside the header shell alongside the desktop navigation). Both components share the same menuId value so the mobile helper can find and mirror the desktop structure at runtime.

In header patterns, the typical structure is:

Header Shell
├── Brand
├── Navigation Menu (desktop, menuId="primary-nav")
│ └── Navigation Menu List ...
├── Actions
└── Navigation Menu Mobile (separate, menuId="primary-nav")

Placement Rules

ComponentPlacementRole
Navigation MenuTop-level wrapper for the desktop navigation.Owns orientation, open-state coordination, viewport behavior, and the menu identity.
Navigation Menu ListDirect child of Navigation Menu.Groups authored top-level menu items. The runtime scans for this list when initializing.
Navigation Menu ItemDirect child of Navigation Menu List.One navigation entry — a plain link, a dropdown parent, or both.
Navigation Menu TriggerInside Navigation Menu Item.Interactive button that opens a dropdown panel.
Navigation Menu ContentInside Navigation Menu Item, alongside a trigger.Dropdown or mega-menu panel content.
Navigation Menu Mobile ItemInside Navigation Menu Content.Declares an explicit mobile tree for complex mega-menu content.
Navigation Menu MobileSeparate from the desktop menu — anywhere on the page, connected by menuId.Mirrors the desktop menu into a drawer-based mobile experience at runtime.

Quick Start

Basic dropdown menu
<OmeNavigationMenu identity='{{"menuId":"primary-nav"}}'>
{#slot default}
<OmeNavigationMenuList>
{#slot default}
<OmeNavigationMenuItem>
{#slot default}
<OmeNavigationMenuTrigger settings='{{"showArrow":true,"triggerAction":"click"}}'>
{#slot default}Products{/slot}
</OmeNavigationMenuTrigger>
<OmeNavigationMenuContent>
{#slot default}
<a href="/shop">Shop all products</a>
{/slot}
</OmeNavigationMenuContent>
{/slot}
</OmeNavigationMenuItem>

<OmeNavigationMenuItem>
{#slot default}
<a href="/about">About</a>
{/slot}
</OmeNavigationMenuItem>
{/slot}
</OmeNavigationMenuList>
{/slot}
</OmeNavigationMenu>
Adding the mobile drawer helper
<!-- Desktop menu -->
<OmeNavigationMenu
identity='{{"menuId":"primary-nav"}}'
settings='{{"useAnimatedMenu":true,"useFullWidth":true,"fullWidthTargetSelector":".site-shell"}}'
>
{#slot default}
<OmeNavigationMenuList>
{#slot default}
<OmeNavigationMenuItem>
{#slot default}
<OmeNavigationMenuTrigger settings='{{"showArrow":true,"triggerAction":"click"}}'>
{#slot default}Services{/slot}
</OmeNavigationMenuTrigger>
<OmeNavigationMenuContent>
{#slot default}
<a href="/services/design">Design</a>
<a href="/services/dev">Development</a>
{/slot}
</OmeNavigationMenuContent>
{/slot}
</OmeNavigationMenuItem>
{/slot}
</OmeNavigationMenuList>
{/slot}
</OmeNavigationMenu>

<!-- Mobile helper (separate, linked by menuId) -->
<OmeNavigationMenuMobile
identity='{{"menuId":"primary-nav"}}'
content='{{"triggerLabel":"Open menu"}}'
/>

The mobile helper reads the desktop menu structure at runtime and generates a drawer-based mobile navigation. No duplicate authoring is needed.


Family Components

The root component wraps all desktop navigation items and manages shared behavior: orientation, keyboard navigation, animated viewport mode, and the menu identity used by the mobile helper. It does not render significant visible UI itself — only a container element and an optional viewport.

Structure Props

PropTypeDefaultDescription
tagstringnav

HTML tag for the root element. Defaults to nav for proper landmark semantics.

Identity Props

PropTypeDefaultDescription
menuIdstring""

Shared menu ID that Navigation Menu Mobile uses to find and connect to this desktop menu. Set this to the same value on both components to enable mobile mirroring.

Settings Props

PropTypeDefaultDescription
orientation"horizontal" | "vertical"horizontal

Root orientation. Affects which arrow keys navigate between triggers and the layout direction. Use vertical for stacked or sidebar menu structures.

useAnimatedMenubooleanfalse

Enables the generated viewport that animates dropdown panel changes. When enabled, content panels are portaled into a viewport container with smooth height transitions and directional motion.

useFullWidthbooleanfalse

Makes the animated viewport align to a wider container instead of the trigger width. Requires useAnimatedMenu to be enabled and a fullWidthTargetSelector to be set.

fullWidthTargetSelectorstring""

CSS selector used to find the container element whose width the viewport should match. Only visible when useFullWidth is enabled. Can be a class selector like .site-shell.

Styling Props

PropTypeDefaultDescription
classclassome-nav-menu-default

CSS class applied to the root element.

viewportClassclassome-nav-menu-default__viewport

CSS class applied to the generated animated viewport container. Only visible when useAnimatedMenu is enabled.

Accessibility Props

PropTypeDefaultDescription
navAriaLabelstringMain

Accessible name applied to the <nav> landmark via aria-label. Set this so screen-reader users can distinguish multiple navigation regions on a page (e.g. "Main", "Footer", "Legal").

Animated Viewport Mode

When useAnimatedMenu is true, the root component generates a viewport container that houses all dropdown panels. This enables:

  • Smooth height transitions as panels open and close.
  • Directional motion animations when switching between panels (content slides in from the direction of the newly focused trigger).
  • Full-width mode that aligns the viewport to a wider parent container for mega-menu layouts.

Animated mode only activates for horizontal orientation and is automatically disabled inside the Etch builder (where it falls back to a non-animated preview).

Keyboard Behavior

When a trigger has focus in a horizontal menu:

KeyAction
Enter / SpaceToggle the associated panel.
ArrowRightMove focus to the next trigger.
ArrowLeftMove focus to the previous trigger.
ArrowDownOpen the dropdown panel.
HomeMove focus to the first trigger.
EndMove focus to the last trigger.
EscapeClose the open panel and return focus to its trigger.

In a vertical menu, ArrowDown / ArrowUp navigate between triggers and ArrowRight opens the panel.

Navigation wraps by default — pressing ArrowRight on the last trigger focuses the first, and vice versa.

By default the menu uses roving tabindex: exactly one top-level trigger is in the Tab order at a time (the others get tabindex="-1"), so a keyboard user Tabs into the menu once and then uses the arrow keys to move between triggers. Inside an open panel, Tab moves naturally through the panel's links (disclosure pattern — the panel is not a focus trap). To restore the legacy behavior where every trigger is individually Tab-reachable, see Backward compatibility filters below.

Accessibility

The navigation uses the disclosure pattern: each trigger is a button that shows/hides a panel of regular links. This is the recommended pattern for website navigation (as opposed to the application-style "menubar" pattern, which requires arrow-key navigation inside the panel and forbids Tab between items).

The runtime wires up ARIA attributes automatically:

  • The root <nav> landmark has an aria-label (default "Main", configurable via accessibility.navAriaLabel) so multiple nav regions can be distinguished.
  • Each trigger has aria-expanded and aria-controls pointing at its sibling content panel's id. The trigger intentionally omits aria-haspopup — the disclosure panel has no role="menu", and per the ARIA spec every aria-haspopup token (including true) would falsely declare a popup type the panel doesn't provide. If you don't set a contentId on the trigger/content pair, the runtime assigns a stable id automatically.
  • Each content panel syncs aria-hidden with its open state. Panels do not carry role="menu" (disclosure pattern) — they are plain containers, and dropdown links are plain <a> elements you can Tab through naturally.
  • data-ome-state attributes (open / closed) are set on items, triggers, and content for CSS targeting.

When a panel opens, focus moves to the first link inside it; pressing Escape (or clicking outside the nav) closes the panel and returns focus to its trigger. Open/close state changes are announced to screen readers via an aria-live region. Background content is not inerted for the desktop menu (it's a disclosure, not a modal).


The list container groups top-level menu items. The runtime scans for the direct Navigation Menu List child when initializing the root component.

Styling Props

PropTypeDefaultDescription
classclassome-nav-menu-default__list

CSS class applied to the list element.

The list always renders as a <ul> element with display: flex and reset list styles. In horizontal mode the flex direction is row; in vertical mode it becomes column.


Each NavigationMenuItem represents one navigation entry. It can contain a plain link, a trigger-plus-content dropdown pair, or a combination of both (a link that also opens a dropdown).

Settings Props

PropTypeDefaultDescription
disabledbooleanfalse

Disables this item and any trigger it contains. Disabled items cannot be opened or focused for activation.

Styling Props

PropTypeDefaultDescription
classclassome-nav-menu-default__item

CSS class applied to the item element.

The item always renders as an <li> element. The runtime tracks open/closed state via data-ome-state on the item.


The interactive button users click or hover to open a dropdown panel. The trigger always renders as a <button> element and can optionally display a built-in chevron icon.

Identity Props

PropTypeDefaultDescription
contentIdstring""

When set, emitted as aria-controls on the trigger so it points at its content panel. Must match identity.contentId on the paired Navigation Menu Content. If left empty, the runtime assigns an id to the content and an aria-controls to the trigger automatically — so you only need to set this if you want predictable, stable ids (e.g. for deep-linking or custom JS).

Settings Props

PropTypeDefaultDescription
disabledbooleanfalse

Prevents this trigger from opening its content panel. Overrides the item-level disabled prop.

showArrowbooleanfalse

Shows a built-in chevron icon inside the trigger button. The chevron rotates 180 degrees when the panel is open.

triggerAction"hover" | "click"hover

Preferred interaction mode. hover opens the panel on mouse enter with a small delay; click requires an explicit click. Inside the Etch builder, all triggers behave as click for reliable editing.

Styling Props

PropTypeDefaultDescription
classclassome-nav-menu-default__trigger

CSS class applied to the trigger button.

Trigger Interaction Modes

  • hover — The panel opens when the cursor enters the trigger area (with a 50ms delay) and closes when the cursor leaves the entire navigation boundary (trigger + content + viewport). This is the default and provides the smoothest desktop experience.
  • click — The panel only opens on explicit click. A document-level click listener closes the panel when clicking outside the navigation root.

Both modes support keyboard activation via Enter / Space.


The dropdown or mega-menu panel paired with a trigger. Content panels are hidden by default and become visible when their sibling trigger opens them.

Identity Props

PropTypeDefaultDescription
contentIdstring""

When set, emitted as the panel's id so the paired trigger's aria-controls can point at it. Must match identity.contentId on the paired Navigation Menu Trigger. If left empty, the runtime assigns an id automatically.

Styling Props

PropTypeDefaultDescription
classclassome-nav-menu-default__content

CSS class applied to the content panel.

Runtime Behavior

  • In non-animated mode, content is positioned absolutely below its trigger item (or to the right in vertical orientation).
  • In animated mode, content is portaled into the shared viewport container and animated with presence state transitions.
  • Content panels sync aria-hidden and data-ome-state between open and closed. They do not carry a role (disclosure pattern — dropdown links are plain <a> elements).
  • The hidden attribute is toggled to control visibility.

A separate component that mirrors the desktop navigation into a drawer-based mobile experience. It is not nested inside Navigation Menu — it is placed elsewhere on the page and connects to the desktop menu via a shared menuId.

At runtime, the mobile helper:

  1. Finds the desktop Navigation Menu root by matching menuId.
  2. Extracts the menu structure recursively (or reads explicit Navigation Menu Mobile Item nodes if present).
  3. Generates a panel-based mobile drawer with sliding navigation and a back button.

In the Etch builder, the helper shows a static preview instead of live-extracted mobile navigation.

Identity Props

PropTypeDefaultDescription
menuIdstringome-mobile-nav

Must match Navigation Menu.identity.menuId. The mobile helper uses this value to find the desktop menu and wire its internal drawer.

Settings Props

PropTypeDefaultDescription
drawerDirection"left" | "right" | "top" | "bottom"left

Direction the mobile drawer slides from.

dismissiblebooleantrue

Allows overlay click and Escape to close the mobile drawer.

Content Props

PropTypeDefaultDescription
showTriggerLabelbooleantrue

When enabled, the trigger displays visible label text. When disabled, only a hamburger icon is shown with a separate triggerAriaLabel for screen readers.

triggerLabelstringMenu

Visible label text and accessible name for the drawer trigger. Only shown when showTriggerLabel is true.

triggerAriaLabelstringMenu

Screen-reader-only label for the trigger button when showTriggerLabel is false.

backButtonLabelstringBack

Fallback label used by the back button when the backButton slot is empty.

Root Styling Props

PropTypeDefaultDescription
classclassome-mobile-nav-default

CSS class applied to the mobile helper wrapper.

Control Styling Props

PropTypeDefaultDescription
triggerClassclassome-mobile-nav-default__trigger

CSS class applied to the drawer trigger button.

backButtonClassclassome-mobile-nav-default__back

CSS class applied to the back button.

closeButtonClassclassome-mobile-nav-default__close

CSS class applied to the icon-only close button.

Drawer Styling Props

PropTypeDefaultDescription
drawerClassclassome-mobile-nav-default__drawer

CSS class applied to the internal drawer root.

headerClassclassome-mobile-nav-default__header

CSS class applied to the drawer header row.

contentClassclassome-mobile-nav-default__content

CSS class applied to the drawer content wrapper.

Menu Styling Props

PropTypeDefaultDescription
listClassclassome-mobile-nav-default__list

CSS class applied to generated root and child lists.

itemClassclassome-mobile-nav-default__item

CSS class applied to generated list items.

linkClassclassome-mobile-nav-default__link

CSS class applied to generated links.

submenuTriggerClassclassome-mobile-nav-default__submenu-trigger

CSS class applied to generated submenu trigger buttons.

nextIconClassclassome-mobile-nav-default__next-icon

CSS class applied to the chevron icon shown on generated submenu triggers.

Slot Styling Props

PropTypeDefaultDescription
beforeClassclassome-mobile-nav-default__before

CSS class applied to the wrapper around the before slot.

afterClassclassome-mobile-nav-default__after

CSS class applied to the wrapper around the after slot.

Slots

SlotDescription
beforeRenders before generated items in the root panel only.
afterRenders after generated items in the root panel only.
backButtonReplaces the default back button content. If omitted, the button falls back to a back icon plus backButtonLabel.

Mobile Keyboard Behavior

Inside the mobile drawer, when a menu item has focus:

KeyAction
ArrowDownMove focus to the next menu item.
ArrowUpMove focus to the previous menu item.
HomeMove focus to the first menu item.
EndMove focus to the last menu item.
Enter / SpaceActivate the focused item (follow link or open submenu).
ArrowRightOpen a submenu (when focused on a submenu trigger).
Escape / ArrowLeftGo back to the previous panel.

How Content Extraction Works

The mobile helper extracts the navigation tree from the desktop menu in one of two ways:

  1. Explicit mobile items — If a Navigation Menu Content subtree contains at least one Navigation Menu Mobile Item, the helper uses only those explicit nodes for that panel and ignores regular auto-generated links.
  2. Automatic extraction — If no explicit mobile items exist, the helper recursively extracts the authored structure: links become mobile links, items with triggers become submenu branches, and nested navigation roots are traversed.

An explicit mobile-only node authored inside Navigation Menu Content when complex mega-menu content needs a custom mobile tree that differs from the auto-extracted structure. If a content panel contains at least one Navigation Menu Mobile Item, the mobile helper uses only those explicit nodes.

Structure Props

PropTypeDefaultDescription
tagstringdiv

HTML tag for the authored wrapper element. Common values are div, ul, li, or a depending on the structural role.

Settings Props

PropTypeDefaultDescription
useAs"trigger" | "link"trigger

Mobile node mode. trigger creates a submenu branch that navigates to a child panel. link creates a direct navigation link.

Content Props

PropTypeDefaultDescription
labelstring""

Label shown in the generated mobile drawer UI for this node.

Link Props

PropTypeDefaultDescription
hrefstring""

Destination URL. Required when useAs is link. Ignored for trigger nodes.

Styling Props

PropTypeDefaultDescription
classclass[]

Optional CSS class list for the authored wrapper element.

When to Use Mobile Items

Use Navigation Menu Mobile Item when your desktop mega-menu has complex content (cards, images, multi-column layouts) that should not be naively extracted into the mobile drawer. By wrapping mobile-relevant content in explicit mobile items, you control exactly what appears in the mobile navigation while keeping the desktop layout free to include richer visual content.


Common Mistakes

Placing items directly inside Navigation Menu

Do not place Navigation Menu Item blocks directly inside Navigation Menu. The runtime scans for a direct Navigation Menu List child at initialization. Always nest items inside a list.

Nesting Navigation Menu Mobile inside Navigation Menu

Navigation Menu Mobile is a separate component. It must not be placed inside the desktop Navigation Menu. Place it elsewhere on the page and connect it via menuId.

Missing trigger for dropdown content

If an item has Navigation Menu Content, it must also have a sibling Navigation Menu Trigger. Content without a trigger will not be openable.

Mismatched menuId values

The menuId on Navigation Menu Mobile.identity.menuId must exactly match Navigation Menu.identity.menuId. If the IDs do not match, the mobile helper will not find the desktop menu and the drawer will be empty.

Duplicating mobile content manually

Do not author a separate second menu tree for mobile. Navigation Menu Mobile derives its structure from the desktop menu automatically. Only use Navigation Menu Mobile Item when you need to customize the mobile tree for complex content.


FAQs

Can I use Navigation Menu without the mobile helper?

Yes. Navigation Menu Mobile is entirely optional. The desktop navigation family works standalone. Only add the mobile helper when you need a drawer-based mobile experience that mirrors the desktop menu.

How does the animated viewport work?

When useAnimatedMenu is enabled on the root component, dropdown content panels are portaled into a shared viewport container. The viewport smoothly transitions its height when panels open and close, and content panels animate with directional motion (slide in from the direction of the newly focused trigger). For full-width mega-menus, enable useFullWidth and set fullWidthTargetSelector to the container the viewport should align to.

What happens when both a link and a trigger are in the same item?

The item renders as a "link-branch" — the link is clickable for navigation and the trigger opens a dropdown. In the mobile drawer, this becomes a row with both a tappable link and a submenu chevron button.

Can I nest navigation menus?

Yes. You can place a Navigation Menu inside a Navigation Menu Content panel for multi-level navigation. Nested menus manage their own state independently. The animated viewport is automatically disabled for nested menus to avoid conflicts.

How do I customize the mobile drawer trigger?

Use showTriggerLabel to toggle between a labeled trigger and an icon-only trigger. When showTriggerLabel is false, set triggerAriaLabel for screen reader accessibility. Style the trigger using controlStyling.triggerClass. For the hamburger icon itself, the component renders a built-in SVG.

When should I use Navigation Menu Mobile Item?

Use it when your desktop mega-menu has complex visual content (cards, multi-column layouts, featured sections) that should not be auto-extracted into the mobile drawer. By wrapping mobile-relevant content in explicit Navigation Menu Mobile Item nodes, you control exactly what appears in the mobile navigation. Simple dropdown menus do not need explicit mobile items — the auto-extraction handles them well.


Backward compatibility filters

The accessibility improvements (roving tabindex, focus moving into the open panel, screen-reader announcements, inert drawer backgrounds) are on by default. If a site depends on the previous keyboard or focus behavior — for example a custom :focus-within CSS rule or JS that walks every trigger via Tab — you can opt out with WordPress filters in your theme's functions.php:

// Global kill-switch: restore ALL legacy accessibility behavior.
add_filter( 'ome_a11y_use_legacy_behavior', '__return_true' );

// Or restore just one feature:
add_filter( 'ome_a11y_roving_tabindex', '__return_false' ); // every trigger Tab-reachable again
add_filter( 'ome_a11y_focus_into_overlay', '__return_false' ); // focus stays on the trigger when a panel opens
add_filter( 'ome_a11y_inert_background', '__return_false' ); // mobile drawer background stays interactive

These filters are part of the released public API and covered by the plugin's backward-compatibility guarantees. The ARIA attribute additions (aria-controls, aria-label on the landmark, content panel ids) are always on regardless of the filters — they're additive and don't change keyboard/focus behavior.