Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Components

Free

Navigation Menu for Vue

Site links in a horizontal bar, some opening a panel of related pages. For a docs or marketing header in Vue with Ark UI, when the item is a destination.

Framework
UI library

Links for the site sit in one row. Some open a panel of related pages, and some are just a link.

Use it for destinations in a header. Commands such as Export or Delete belong in a Dropdown Menu. In Vue, Ark UI renders a navigation landmark, and router links go in the link part so the focus behavior stays.

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

Anatomy

Navigation Menu anatomy: list, trigger, indicator, content, link
NavigationMenu
├── NavigationMenuList
│   ├── NavigationMenuItem
│   │   ├── NavigationMenuTrigger
│   │   └── NavigationMenuContent
│   │       └── NavigationMenuLink
│   └── NavigationMenuItem
│       └── NavigationMenuLink
└── NavigationMenuIndicator

Usage

<script setup lang="ts">
import {
  NavigationMenu, NavigationMenuContent, NavigationMenuItem,
  NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger,
} from "@/components/ui/navigation-menu";
</script>
<template>
  <NavigationMenu>
    <NavigationMenuList>
      <NavigationMenuItem>
        <NavigationMenuTrigger>Item One</NavigationMenuTrigger>
        <NavigationMenuContent>
          <NavigationMenuLink href="#">Link</NavigationMenuLink>
        </NavigationMenuContent>
      </NavigationMenuItem>
    </NavigationMenuList>
  </NavigationMenu>
</template>

Accessibility

Enter
Activate the focused control.
Space
Activate the focused control.
Arrow Left
Move left.
Arrow Right
Move right.
Escape
Close the open panel.

Demos

Rtl

The header in Arabic: a getting-started list, a component grid that wraps into columns, and a docs link with no panel. dir is rtl on the wrapper, so the triggers run from the right and panels follow the reading order. Arrow keys follow that order too. Set direction on the container you ship, and check that a wide panel does not spill off the far edge.

Preview width

FAQ

How do I highlight the current page in the Navigation Menu?

Set aria-current="page" on the link that matches the current route. Screen readers announce it, and you can style it with Tailwind's aria-[current=page]: variant.

How do I change how fast the Ark UI Navigation Menu opens on hover?

It doesn't open on hover: each panel opens on click, so there's no delay to tune and nothing pops open as the pointer passes over the bar.

How do I control which Navigation Menu panel is open in Vue?

Each NavigationMenuItem is its own popover: bind v-model:open on the item you want to control.

How do I make the Navigation Menu work on mobile?

Hide the bar below md and put the same links in a Sheet behind a menu button. Wide panels under a header bar don't suit small touch screens, and a sheet gives every link a full-width tap target.

When should I use a Navigation Menu instead of a Dropdown Menu or a Menubar?

Use the Navigation Menu for site navigation: links in a header, grouped under triggers. A Dropdown Menu holds actions like Export or Delete, and a Menubar is an app's File, Edit, View bar. Each gives assistive tech the roles that match what it does.

Is the Ark UI Navigation Menu accessible?

Yes. It renders a nav landmark, Tab moves between triggers and links, Enter or Space opens a trigger's panel, and Escape closes it and returns focus to the trigger. Give each link text that makes sense on its own, since screen readers can list them out of context.

How do I install the Navigation Menu in Vue?

Run the command from the Installation section above: it adds the Navigation Menu and the parts it uses to your project's ui folder. shadcn-vue writes components/ui/navigation-menu.ts, a file you own and can edit.

API Reference

<nav> data-slot="navigation-menu"

PropType
class?string

Merged with the part's classes.

<li>

PropType
open?boolean

The controlled open state of the popover. Two-way with v-model:open.

defaultOpen?boolean

The initial open state of the popover when rendered. Use when you don't need to control the open state of the popover.

triggerValue?string | null

The controlled trigger value. Two-way with v-model:triggerValue.

Type
string | null
defaultTriggerValue?string | null

The initial trigger value when rendered. Use when you don't need to control the trigger value.

Type
string | null
autoFocus?boolean

Whether to automatically set focus on the first focusable content within the popover when opened.

Default
true
closeOnEscape?boolean

Whether to close the popover when the escape key is pressed.

Default
true
closeOnInteractOutside?boolean

Whether to close the popover when the user clicks outside of the popover.

Default
true
dir?"ltr" | "rtl"

The document's text/writing direction.

Type
"ltr" | "rtl"
Default
"ltr"
finalFocusEl?() → MaybeElement

Element to receive focus when the popover is closed.

Returns
MaybeElement
hideMode?"display-none" | "activity"

How to hide content when mounted but not present. - display-none: HTML hidden attribute. Effects stay alive. - activity: React 19 <Activity mode="hidden">. Effects pause. Requires React 19+.

Type
"display-none" | "activity"
Default
"display-none"
id?string

The unique identifier of the machine.

ids?object

The ids of the elements in the popover. Useful for composition.

Type
{ anchor?: string; trigger?: string | ((value?: string) → string); content?: string; title?: string; description?: string; closeTrigger?: string; positioner?: string; arrow?: string }
initialFocusEl?() → HTMLElement | null | false

The element to focus on when the popover is opened. Return false to skip moving focus, or null to use the default.

Type
() → HTMLElement | null | false
Returns
HTMLElement | null | false
lazyMount?boolean

Whether to enable lazy mounting. Ark default: false.

Default
true
modal?boolean

Whether the popover should be modal. When set to true: - interaction with outside elements will be disabled - only popover content will be visible to screen readers - scrolling is blocked - focus is trapped within the popover

Default
false
persistentElements?Array<() → Element | null>

Returns the persistent elements that: - should not have pointer-events disabled - should not trigger the dismiss event

Type
Array<() → Element | null>
portalled?boolean

Whether the popover is portalled. This will proxy the tabbing behavior regardless of the DOM position of the popover content.

Default
true
positioning?object

The user provided options used to position the popover content.

Type
{ restoreStyles?: boolean; applyStyles?: boolean; hideWhenDetached?: boolean; strategy?: "absolute" | "fixed"; placement?: Placement; offset?: { mainAxis?: number; crossAxis?: number; }; gutter?: number; shift?: number; overflowPadding?: number; arrowPadding?: number; flip?: boolean | Placement[]; slide?: boolean; overlap?: boolean; sameWidth?: boolean; fitViewport?: boolean; sizeMiddleware?: boolean; boundary?: (() → Boundary) | Boundary | "clipping-ancestors"; listeners?: boolean | AutoUpdateOptions; onComplete?: (data: ComputePositionReturn) → void; onPositioned?: ((data: { placed: boolean; }) → void); getAnchorElement?: () → HTMLElement | VirtualElement | null; getAnchorRect?: (element: HTMLElement | VirtualElement | null) → AnchorRect | null; updatePosition?: ((data: { updatePosition: () → Promise<void>; floatingElement: HTMLElement | null; }) → void | Promise<void>) }
Default
{ gutter: 6, placement: "bottom-start" }
restoreFocus?boolean

Whether to restore focus to the element that had focus before the popover was opened.

Default
true
skipAnimationOnMount?boolean

Whether to allow the initial presence animation.

Default
false
translations?object

Specifies the localized strings that identifies the accessibility elements and their states.

Type
{ closeTriggerLabel?: string }
unmountOnExit?boolean

Whether to unmount on exit. Ark default: false.

Default
true
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

class?string

Merged with the part's classes.

EventType
@open-change(details: OpenChangeDetails) → void

Function invoked when the popover opens or closes.

Type
(details: OpenChangeDetails) → void
Parameters

details – { open: boolean }

Returns
void
@trigger-value-change(details: TriggerValueChangeDetails) → void

Function called when the trigger value changes.

Type
(details: TriggerValueChangeDetails) → void
Parameters

details – { value: string | null; triggerElement: HTMLElement | null }

Returns
void
@escape-key-down(event: KeyboardEvent) → void

Function called when the escape key is pressed.

Parameters

event – { altKey: boolean; charCode: number; ctrlKey: boolean; code: string; getModifierState: (key: string) → boolean; key: string; keyCode: number; locale: string; location: number; metaKey: boolean; repeat: boolean; shiftKey: boolean; which: number }

Returns
void
@focus-outside(event: FocusOutsideEvent) → void

Function called when the focus is moved outside the component.

Type
(event: FocusOutsideEvent) → void
Parameters

event – FocusOutsideEvent

Returns
void
@interact-outside(event: InteractOutsideEvent) → void

Function called when an interaction happens outside the component.

Type
(event: InteractOutsideEvent) → void
Parameters

event – InteractOutsideEvent

Returns
void
@pointer-down-outside(event: PointerDownOutsideEvent) → void

Function called when the pointer is pressed down outside the component.

Type
(event: PointerDownOutsideEvent) → void
Parameters

event – PointerDownOutsideEvent

Returns
void
@request-dismiss(event: LayerDismissEvent) → void

Function called when this layer is closed due to a parent layer being closed.

Type
(event: LayerDismissEvent) → void
Parameters

event – LayerDismissEvent

Returns
void

<button> data-slot="navigation-menu-trigger"

PropType
value?string

The value that identifies this specific trigger.

asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

class?string

Merged with the part's classes.

AttributeType
data-current?string

Present when current.

data-placement?string

The placement of the trigger.

data-side?string

The side of the trigger that the trigger is positioned on.

data-state"open" | "closed"

"open" or "closed".

Type
"open" | "closed"
data-value?string

The value of the item.

<div> data-slot="navigation-menu-positioner"

PropType
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

class?string

Merged with the part's classes.

AttributeType
data-has-nested?string

Present when the positioner has nested popovers.

data-nested?string

Present when the positioner is nested within another popover.

CSS variableType
--available-heightstring

The available height in viewport.

--available-widthstring

The available width in viewport.

--reference-heightstring

The height of the root.

--reference-widthstring

The width of the reference element.

--transform-originstring

The transform origin for animations.

--xstring

The x position for transform.

--ystring

The y position for transform.

--z-indexstring

The z-index value.

<div> data-slot="navigation-menu-content"

PropType
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

class?string

Merged with the part's classes.

AttributeType
data-expanded?string

Present when expanded.

data-has-nested?string

Present when another popover is nested inside it.

data-nested?string

Present when it's nested inside another popover.

data-placementstring

The placement of the content.

data-side"top" | "right" | "bottom" | "left"

The side of the trigger that the content is positioned on.

Type
"top" | "right" | "bottom" | "left"
data-state"open" | "closed"

"open" or "closed".

Type
"open" | "closed"
CSS variableType
--layer-indexstring

The index of the dismissable in the layer stack.

--nested-layer-countstring

The number of nested popovers.

--transform-originstring

Where the content scales from, on its trigger's side.

<ul> data-slot="navigation-menu-list"

PropType
class?string

Merged with the part's classes.

<a> data-slot="navigation-menu-link"

PropType
asChild?boolean

Use the provided child element as the default rendered element, combining their props and behavior.

class?string

Merged with the part's classes.

<div> data-slot="navigation-menu-indicator"

PropType
class?string

Merged with the part's classes.

<div> data-slot="navigation-menu-viewport"

PropType
class?string

Merged with the part's classes.

With other components

  • Button

    Buttons

    Trigger looks like a button

  • Sheet

    Overlays

    Same links on phones

  • Badge

    Feedback

    New next to a link

  • Card

    Layout

    Featured cell in mega menu

  • Separator

    Layout

    Split groups in a panel

  • Breadcrumb

    Navigation

    Trail under the header