NavigationMenu
elements man ui/templates/navigation-menuSite navigation with large animated dropdown panels.
Exampleimport { NavigationMenu, NavigationMenuItem, NavigationMenuLink, navigationMenuTriggerClass, } from "@elements/ui/navigation-menu"; <NavigationMenu indicator> <NavigationMenuItem label="Getting started"> <ul class="nav-grid"> <li><NavigationMenuLink href="/docs">Introduction</NavigationMenuLink></li> <li><NavigationMenuLink href="/docs/install">Installation</NavigationMenuLink></li> </ul> </NavigationMenuItem> <NavigationMenuItem label="Components">...</NavigationMenuItem> <NavigationMenuItem> <NavigationMenuLink href="/docs" trigger>Docs</NavigationMenuLink> </NavigationMenuItem> </NavigationMenu>
Parts
| Part | What it is |
|---|---|
NavigationMenu |
The root <nav>: owns value (the open item), renders the list, the optional indicator and the shared viewport. |
NavigationMenuItem |
A list item. With label it is a trigger button and a panel (its default slot); without, it holds a link. |
NavigationMenuLink |
A link. Choosing it closes the menu. active marks the current page (aria-current=page). trigger gives it the trigger look for the top list. |
NavigationMenu sub |
A NavigationMenu with sub inside a panel is a submenu: one item always open, panels inline, switching on hover or click. |
.navigation-menu-viewport |
The box under the open panel; it animates between panels' sizes (--navigation-menu-viewport-width and -height). |
.navigation-menu-indicator |
The arrow under the open trigger, moved with --navigation-menu-indicator-translate-x (or -y) and sized with --navigation-menu-indicator-size. |
Attributes
NavigationMenu
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
string (two-way) |
"" |
The open item's value; empty when closed. Bind it to open an item from code. |
delay |
number |
200 |
Hover time in ms before a panel opens. |
skipDelay |
number |
300 |
Within this many ms of a close, hovering opens at once, so moving across triggers does not wait each time. |
closeDelay |
number |
150 |
Ms after the pointer leaves the trigger and panel before the panel closes. |
orientation |
"horizontal" | "vertical" |
"horizontal" |
Horizontal opens panels below; vertical stacks the list and opens panels beside it. |
viewport |
boolean |
true |
Show panels in one shared, animated box under the list. false places each panel under its own trigger with its own box. |
indicator |
boolean |
false |
Show an arrow under the open trigger. |
arrow |
boolean |
false |
Draw an arrow on the viewport's edge that points at the open trigger. |
arrowPadding |
number |
5 |
px the viewport arrow keeps from the viewport's corners. |
sub |
boolean |
false |
This menu is a submenu inside a panel: one item is always open and its panel shows inline. |
align |
"start" | "center" | "end" |
"start" |
Where the viewport (or a panel) sits along the list. |
sideOffset |
number |
6 |
Gap between the list and the viewport, in px. |
alignOffset |
number |
0 |
Moves the viewport (or a panel) along the list from its aligned spot, in px. |
positionMethod |
"fixed" | "absolute" |
"fixed" |
"absolute" places the viewport in page coordinates instead of viewport ones. |
disableAnchorTracking |
boolean |
false |
Place the viewport once when it opens; it stays put if its anchor moves, and is placed again on a scroll or resize. |
collisionBoundary |
string |
"" |
A CSS selector for boxes whose edges count like the screen's when placing the viewport. |
sticky |
"partial" | "always" |
"partial" |
always keeps the viewport in view when the list scrolls away; otherwise it hides with data-anchor-hidden. |
anchor |
string | Element | null |
null |
An element, or a selector for one, the viewport is placed against instead of the list, such as a whole header. |
collisionPadding |
number |
8 |
px the viewport keeps from the screen's edges and the collision boundary. |
collisionAvoidance |
{ side?: "flip" | "shift" | "none"; align?: "flip" | "shift" | "none"; fallbackAxisSide?: "start" | "end" | "none" } | null |
null |
How the viewport avoids the edges. By default it keeps its side and slides along it; side "flip" opens it above the list when there is no room below. |
backdrop |
boolean |
false |
Render a layer under the open panel that dims the page, cut away over the list; a press on it closes the menu. |
dir |
"" | "ltr" | "rtl" |
"" |
Reading direction for the arrow keys. Empty follows the page. |
label |
string |
"Main" |
aria-label of the <nav> landmark. |
class |
string |
"" |
Extra classes on the <nav>. |
onValueChange |
(value: string) => void |
() => {} |
Called when the menu opens, switches or closes a panel itself. |
onOpenChangeComplete |
(open: boolean) => void |
() => {} |
Called once a panel has finished opening from a closed menu (true), and once the last panel and the viewport have finished closing (false). |
onEscapeKeyDown, onPointerDownOutside, onFocusOutside, onInteractOutside |
(e) => void |
() => {} |
Called before the open panel closes that way; e.preventDefault() keeps it open. |
NavigationMenuItem
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
string |
an id |
Names the item for the root's value. |
label |
string |
"" |
Makes the item a trigger with this text and its default slot a panel. The trigger slot replaces the text. |
disabled |
boolean |
false |
The trigger does not open its panel. |
keepMounted |
boolean |
false |
The panel renders with the page and stays in the DOM, hidden, while closed, so its links are there for crawlers. |
class |
string |
"" |
Extra classes on the panel. |
attrs |
Record<string, string | number | boolean | undefined> |
{} |
Other attributes for the trigger button. |
NavigationMenuLink
| Attribute | Type | Default | Description |
|---|---|---|---|
href |
string |
(required) | Required. Where the link goes. |
active |
boolean |
false |
The current page: aria-current=page and data-active. |
trigger |
boolean |
false |
Style the link like a trigger, for a link in the top list. |
closeOnClick |
boolean |
true |
false follows the link and leaves the menu open. |
onSelect |
(e: Event) => void |
() => {} |
Called when the link is chosen; e.preventDefault() keeps the menu open and cancels the navigation. |
class, attrs |
string, Record |
"", {} |
Extra classes and attributes on the <a>. |
Slots
| Slot | Part | Content |
|---|---|---|
default |
NavigationMenu |
The NavigationMenuItem elements. |
default |
NavigationMenuItem |
With label: the panel's content. Without: the item's link. |
trigger |
NavigationMenuItem |
Rich trigger content; label is still needed (it makes the item a trigger). |
default |
NavigationMenuLink |
The link's content. |
Keyboard
| Keys | Action |
|---|---|
Tab / Shift + Tab |
Moves through triggers, links and an open panel's links in page order. |
Enter / Space |
On a trigger: opens or closes its panel. |
ArrowRight / ArrowLeft |
The next or previous trigger or link in the list (flipped under rtl). |
ArrowDown / ArrowUp |
The next or previous trigger or link, in either orientation, unless ArrowDown enters an open panel. |
ArrowDown (horizontal), ArrowRight (vertical) |
On an open trigger: moves into its panel. |
Home / End |
The first or last trigger or link in the list. |
Arrow keys in a panel |
The next or previous link or control in the panel. |
Escape |
Closes the panel and focuses its trigger. |
Data attributes
| Attribute | Values | On | Description |
|---|---|---|---|
data-orientation |
"horizontal" | "vertical" |
nav, list, indicator, viewport | |
data-state |
"open" | "closed" |
trigger, panel, viewport | |
data-pressed |
present | trigger | While the panel a click of the trigger opened is open (not a hover). |
data-instant |
"click" | "dismiss" |
viewport, panel | click when a keyboard press opened it, dismiss when Escape closed it. |
data-side, data-align |
placement | viewport, panel, arrow | |
data-uncentered |
present | arrow | When the corners hold the arrow off the trigger's center. |
data-anchor-hidden |
present | viewport, panel | While the list is scrolled out of view (unless sticky is always). |
--positioner-width / --positioner-height |
px | viewport | Its own size while open. |
data-state, data-starting-style, data-ending-style |
"open" | "closed" |
.navigation-menu-backdrop | With backdrop. |
data-disabled |
present | trigger | |
data-motion |
"from-start" | "from-end" | "to-start" | "to-end" |
panel | Which way a panel enters or leaves when the open item changes. |
data-state |
"visible" | "hidden" |
indicator | |
data-active |
present | NavigationMenuLink | The current page. |
Accessibility
- A
<nav>landmark (aria-label, default Main) with a list of triggers and links: not role=menu, since site navigation is a set of disclosures (WAI-ARIA disclosure navigation). - Each trigger is a
<button>with aria-expanded and aria-controls; its panel follows it in the DOM, so Tab reaches the panel's links right after the trigger. - Escape closes the panel and returns focus to its trigger; focus or a press outside the navigation closes it.
- NavigationMenuLink active sets aria-current=page.
- Panels are popover=manual in the top layer: never clipped, no portal.
See also
- DropdownMenu (opens in a new tab): A menu of actions from a button, with submenus, checkbox and radio items.
- ContextMenu (opens in a new tab): A menu at the pointer on right-click or long-press.
- Menubar (opens in a new tab): A desktop-style bar of menus that stays open as you move across it.