Accessible, unstyled site navigation with hover-intent flyouts, an indicator, and a measured viewport.
|
|
| Package |
@vizejs/ui/navigation-menu |
| Maturity |
stable |
| Own the source |
vize lib pull navigation-menu |
| Requires |
collection, context, controllable-state, id |
| Aliases |
site navigation, mega menu, navbar, flyout navigation, disclosure navigation |
| Covers |
WAI-ARIA disclosure navigation pattern, Radix Navigation Menu, Reka UI Navigation Menu, Ark UI Navigation Menu |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { NavigationMenu, NavigationMenuRoot, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport } from "@vizejs/ui/navigation-menu";
Or copy the source into your project with vize lib pull navigation-menu (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Site navigation with two flyout sections, a direct link, an indicator, and a shared viewport. -->
<script setup lang="ts">
import {
NavigationMenuContent,
NavigationMenuIndicator,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuRoot,
NavigationMenuTrigger,
NavigationMenuViewport,
} from "@vizejs/ui/navigation-menu";
</script>
<template>
<NavigationMenuRoot aria-label="Main">
<NavigationMenuList>
<NavigationMenuItem value="products">
<NavigationMenuTrigger>Products</NavigationMenuTrigger>
<NavigationMenuContent>
<ul>
<li><NavigationMenuLink href="/products/analytics">Analytics</NavigationMenuLink></li>
<li><NavigationMenuLink href="/products/automation">Automation</NavigationMenuLink></li>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem value="resources">
<NavigationMenuTrigger>Resources</NavigationMenuTrigger>
<NavigationMenuContent>
<ul>
<li><NavigationMenuLink href="/docs">Documentation</NavigationMenuLink></li>
<li><NavigationMenuLink href="/blog">Blog</NavigationMenuLink></li>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem value="pricing">
<NavigationMenuLink href="/pricing" active>Pricing</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuIndicator />
</NavigationMenuList>
<NavigationMenuViewport />
</NavigationMenuRoot>
</template>
API
Source: src/families/navigation/navigation-menu/navigation-menu-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned base id. null and undefined select a deterministic fallback. |
modelValue |
NavigationMenuValue |
undefined |
Controlled open item value (v-model). undefined selects uncontrolled behavior. |
defaultValue |
NavigationMenuValue |
null |
Initially open item value for uncontrolled use. |
delayDuration |
number |
200 |
Hover time in milliseconds before a pointer opens a flyout. |
skipDelayDuration |
number |
300 |
Window in milliseconds after closing during which hovering another trigger opens instantly. |
closeDelay |
number |
150 |
Grace time in milliseconds before a pointer leaving the trigger or flyout closes it. |
orientation |
NavigationMenuOrientation |
"horizontal" |
Layout axis of the top-level list, used for arrow keys and the open key. |
dir |
NavigationMenuDirection |
undefined |
Reading direction for horizontal arrow keys and motion attributes. undefined inherits DirectionProvider/LocaleProvider, then "ltr". |
ariaLabel |
string |
undefined |
Accessible name of the navigation landmark. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the landmark. |
Events
| Event |
Payload |
Description |
update:modelValue |
[value: NavigationMenuValue] |
Fired with the next open item value whenever a flyout opens or closes. |
change |
[ value: NavigationMenuValue, previous: NavigationMenuValue, reason: NavigationMenuChangeReason, ] |
Fired after a distinct open-value change with the input that caused it. |
Slots
| Slot |
Slot props |
Description |
default |
NavigationMenuSlotState |
List, item, and viewport children. Receives the open value and axis. |
Exposed
| Member |
Type |
Description |
close |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
open |
— |
|
value |
— |
|
Source: src/families/navigation/navigation-menu/navigation-menu-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned base id. null and undefined select a deterministic fallback. |
modelValue |
NavigationMenuValue |
undefined |
Controlled open item value (v-model). undefined selects uncontrolled behavior. |
defaultValue |
NavigationMenuValue |
null |
Initially open item value for uncontrolled use. |
delayDuration |
number |
200 |
Hover time in milliseconds before a pointer opens a flyout. |
skipDelayDuration |
number |
300 |
Window in milliseconds after closing during which hovering another trigger opens instantly. |
closeDelay |
number |
150 |
Grace time in milliseconds before a pointer leaving the trigger or flyout closes it. |
orientation |
NavigationMenuOrientation |
"horizontal" |
Layout axis of the top-level list, used for arrow keys and the open key. |
dir |
NavigationMenuDirection |
undefined |
Reading direction for horizontal arrow keys and motion attributes. undefined inherits DirectionProvider/LocaleProvider, then "ltr". |
ariaLabel |
string |
undefined |
Accessible name of the navigation landmark. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the landmark. |
Events
| Event |
Payload |
Description |
update:modelValue |
[value: NavigationMenuValue] |
Fired with the next open item value whenever a flyout opens or closes. |
change |
[ value: NavigationMenuValue, previous: NavigationMenuValue, reason: NavigationMenuChangeReason, ] |
Fired after a distinct open-value change with the input that caused it. |
Slots
| Slot |
Slot props |
Description |
default |
NavigationMenuSlotState |
List, item, and viewport children. Receives the open value and axis. |
Exposed
| Member |
Type |
Description |
element |
HTMLElement | null |
Rendered navigation landmark. |
value |
NavigationMenuValue |
Open item value. |
open |
(value: string) => boolean |
Open one item's flyout immediately. |
close |
() => boolean |
Close the open flyout immediately. |
Source: src/families/navigation/navigation-menu/navigation-menu-content.vue
Props
| Prop |
Type |
Default |
Description |
forceMount |
boolean |
false |
Keep closed flyout content rendered (inside the hidden container) for exit animations or crawlable links. Without it, closed flyouts keep an empty hidden container. |
Slots
| Slot |
Slot props |
Description |
default |
NavigationMenuItemSlotState |
Flyout content, usually NavigationMenuLink lists. Receives the open state. |
Source: src/families/navigation/navigation-menu/navigation-menu-indicator.vue
Slots
| Slot |
Slot props |
Description |
default |
NavigationMenuMeasuredSlotState |
Optional indicator content such as an arrow. Receives the open value. |
Source: src/families/navigation/navigation-menu/navigation-menu-item.vue
Props
| Prop |
Type |
Default |
Description |
value (required) |
string |
required |
Stable item value used as the open value of its flyout. |
Slots
| Slot |
Slot props |
Description |
default |
NavigationMenuItemSlotState |
Trigger, content, or a plain link. Receives the open state of this item. |
Source: src/families/navigation/navigation-menu/navigation-menu-link.vue
Props
| Prop |
Type |
Default |
Description |
as |
PrimitiveAs |
"a" |
Native element, custom element, or router component to render. |
href |
string |
undefined |
Native link destination. Router components can receive their own route attrs instead. |
active |
boolean |
false |
Whether the link represents the current page (aria-current="page"). |
Events
| Event |
Payload |
Description |
select |
[nativeEvent: MouseEvent] |
Fired when the link is activated. Call preventDefault() to keep the flyout open. |
Slots
| Slot |
Slot props |
Description |
default |
NavigationMenuLinkSlotState |
Link content. Receives whether it represents the current page. |
Source: src/families/navigation/navigation-menu/navigation-menu-list.vue
Slots
| Slot |
Slot props |
Description |
default |
{ /** Layout axis of the list. */ readonly orientation: NavigationMenuOrientation; } |
NavigationMenuItem children. Receives the list axis. |
Source: src/families/navigation/navigation-menu/navigation-menu-trigger.vue
Props
| Prop |
Type |
Default |
Description |
disabled |
boolean |
false |
Disable the trigger and its flyout. |
Slots
| Slot |
Slot props |
Description |
default |
NavigationMenuItemSlotState |
Trigger label. Receives the open state of the owning item. |
Source: src/families/navigation/navigation-menu/navigation-menu-viewport.vue
Slots
| Slot |
Slot props |
Description |
default |
NavigationMenuMeasuredSlotState |
Optional backdrop content. Receives the open value. |
Behavior
Normative state x input -> outcome table for the @vizejs/ui/navigation-menu
parts: navigation-menu-root.vue, navigation-menu-list.vue,
navigation-menu-item.vue, navigation-menu-trigger.vue,
navigation-menu-content.vue, navigation-menu-link.vue,
navigation-menu-indicator.vue, and navigation-menu-viewport.vue. Every row
is proven by the named test in navigation-menu.test.ts or
navigation-menu-ssr.test.ts; compile-only guarantees live in
navigation-menu.types.test-d.ts.
The menu follows the APG disclosure navigation pattern (not role="menu"): a
labelled <nav> with a <ul> of items, native <button aria-expanded aria-controls> triggers, and flyouts rendered inline after their trigger so
the native tab order reaches them. The viewport is a measured backdrop that
publishes --vize-navigation-menu-viewport-width/height; the indicator
publishes --vize-navigation-menu-indicator-offset/size. Both are measured on
the client only.
| ID |
State |
Input |
Outcome |
Evidence |
| N1 |
closed |
render |
labelled nav, deterministic trigger/content ids, closed flyouts unmounted, aria-current links |
renders a labelled disclosure navigation with wired triggers and closed flyouts |
| N2 |
any |
trigger click |
toggles the flyout inline after the trigger and reports toggle |
click toggles a flyout inline after its trigger |
| N3 |
mouse or pen |
hover, move, leave, re-enter |
opens after delayDuration, switches instantly, closes after closeDelay, skip window reopens |
hover opens after the delay, skips it between triggers, and closes after a grace period |
| N4 |
touch |
pointerenter |
hover intent is ignored; touch uses click |
touch pointers never open on hover |
| N5 |
focus on a list entry |
arrows, Home, End, open key |
roving between triggers and top-level links (wrapping); ArrowDown opens and focuses the flyout |
arrow keys move between top-level entries and the open key enters the flyout |
| N6 |
open |
Escape, outside pointer, focus out, link |
dismisses; Escape restores focus to the trigger; flyout links close with reason link |
Escape, outside pointerdown, focus leaving, and link selection dismiss the flyout |
| N7 |
switching items |
open another item, forceMount |
data-motion reports from/to start/end; force-mounted flyouts stay hidden while closed |
motion attributes follow the direction between items and forceMount keeps hidden flyouts |
| N8 |
controlled / exposed root |
click, setProps, open/close |
the parent owns the open value; imperative open/close report distinct changes |
controlled values wait for the parent and the root exposes open/close |
| N9 |
open |
measure |
indicator and viewport publish CSS variables for the open item and clear them when closed |
the indicator and viewport publish geometry for the open item |
| N10 |
disabled trigger |
click, hover, open key |
the flyout never opens |
disabled triggers ignore click, hover, and the open key |
| N11 |
missing provider |
setup |
parts fail closed with the shared context diagnostic |
compound parts require matching providers |
| N12 |
SSR and hydration |
isolated render/mount |
default-open flyouts render, geometry is omitted, hydration has no warnings |
navigation-menu-ssr.test.ts |
| N13 |
link href / as |
render |
script-capable hrefs are dropped; as renders router or custom elements |
links drop script-capable hrefs and render custom elements |