Navigation Menu

A collection of links for navigating websites.

<script setup lang="ts">
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
  navigationMenuTriggerStyle,
} from '@/components/ui/navigation-menu'
import ListItem from './navigation-menu-list-item.vue'

const components: { title: string, href: string, description: string }[] = [
  {
    title: 'Alert Dialog',
    href: '/docs/primitives/alert-dialog',
    description:
      'A modal dialog that interrupts the user with important content and expects a response.',
  },
  {
    title: 'Hover Card',
    href: '/docs/primitives/hover-card',
    description:
      'For sighted users to preview content available behind a link.',
  },
  {
    title: 'Progress',
    href: '/docs/primitives/progress',
    description:
      'Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.',
  },
  {
    title: 'Scroll-area',
    href: '/docs/primitives/scroll-area',
    description: 'Visually or semantically separates content.',
  },
  {
    title: 'Tabs',
    href: '/docs/primitives/tabs',
    description:
      'A set of layered sections of content—known as tab panels—that are displayed one at a time.',
  },
  {
    title: 'Tooltip',
    href: '/docs/primitives/tooltip',
    description:
      'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.',
  },
]
</script>

<template>
  <NavigationMenu>
    <NavigationMenuList>
      <NavigationMenuItem>
        <NavigationMenuTrigger>Getting started</NavigationMenuTrigger>
        <NavigationMenuContent>
          <ul class="w-96">
            <ListItem href="/docs" title="Introduction">
              Re-usable components built with Tailwind CSS.
            </ListItem>
            <ListItem href="/docs/installation" title="Installation">
              How to install dependencies and structure your app.
            </ListItem>
            <ListItem href="/docs/primitives/typography" title="Typography">
              Styles for headings, paragraphs, lists...etc
            </ListItem>
          </ul>
        </NavigationMenuContent>
      </NavigationMenuItem>
      <NavigationMenuItem class="hidden md:flex">
        <NavigationMenuTrigger>Components</NavigationMenuTrigger>
        <NavigationMenuContent>
          <ul class="grid w-[400px] gap-2 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
            <ListItem
              v-for="component in components"
              :key="component.title"
              :title="component.title"
              :href="component.href"
            >
              {{ component.description }}
            </ListItem>
          </ul>
        </NavigationMenuContent>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuLink as-child :class="navigationMenuTriggerStyle()">
          <a href="/docs">Docs</a>
        </NavigationMenuLink>
      </NavigationMenuItem>
    </NavigationMenuList>
  </NavigationMenu>
</template>

Installation

Install the packages

npm install @animate-ui/vue motion-v
pnpm add @animate-ui/vue motion-v
yarn add @animate-ui/vue motion-v
bun add @animate-ui/vue motion-v

Add the shadcn-vue component

npx shadcn-vue@latest add navigation-menu
pnpm dlx shadcn-vue@latest add navigation-menu
yarn dlx shadcn-vue@latest add navigation-menu
bun x shadcn-vue@latest add navigation-menu

Animate it

The CLI finds your shadcn-vue components through components.json, swaps their primitive imports for Animate UI's and removes the CSS animation classes Motion replaces.

npx @animate-ui/cli@latest animate navigation-menu
pnpm dlx @animate-ui/cli@latest animate navigation-menu
yarn dlx @animate-ui/cli@latest animate navigation-menu
bun x @animate-ui/cli@latest animate navigation-menu

Usage

<script setup lang="ts">
import {
  NavigationMenuContent,
  NavigationMenuIndicator,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuRoot,
  NavigationMenuTrigger,
  NavigationMenuViewport,
} from '@animate-ui/vue/primitives/reka-ui';
</script>

<template>
  <NavigationMenuRoot>
    <NavigationMenuList>
      <NavigationMenuItem>
        <NavigationMenuTrigger>Learn</NavigationMenuTrigger>
        <NavigationMenuContent>
          <NavigationMenuLink href="/docs">Documentation</NavigationMenuLink>
        </NavigationMenuContent>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuLink href="/blog">Blog</NavigationMenuLink>
      </NavigationMenuItem>
      <NavigationMenuIndicator />
    </NavigationMenuList>
    <NavigationMenuViewport
      class="h-(--reka-navigation-menu-viewport-height) w-(--reka-navigation-menu-viewport-width)"
    />
  </NavigationMenuRoot>
</template>

Animation

The motion of Animate UI's React Navigation Menu:

  • Viewport fades and scales in from 0.9, dropping from its top edge, in 220ms ({ duration: 0.22, ease: [0.215, 0.61, 0.355, 1] }, a soft ease-out), like the Dropdown Menu's content, and closes the same way before it unmounts.
  • Stagger: opening from closed, the content's links come in one after another, 33ms apart (closer past 10 rows: 33ms × √(10 / rows), so a long list doesn't wait): each slides 8px in from the right and clears from a 4px blur as it fades in, in 0.3s on a soft ease-out ({ duration: 0.3, ease: [0.215, 0.61, 0.355, 1] }). The box itself is there at once (its opacity in 0.1s, its scale as before), so the rows bring the fade, and a disabled row comes in at its own opacity. Moving to another item doesn't stagger again, closing shows every link at once, and under reduced motion nothing staggers.
  • Moving from one item to another, the Viewport's size follows the new content instead of jumping, on a spring (stiffness 660, damping 52): it starts gently, is most of the way there in ~155ms and eases in without going past, settled by ~300ms, keeping its speed when it changes again before it has landed (never so fast it would go past the next size): Animate UI animates --reka-navigation-menu-viewport-width and --reka-navigation-menu-viewport-height, so classes reading them resize smoothly.
  • reka-ui places the Viewport under the open trigger (--reka-navigation-menu-viewport-left): moving to another item, it glides there on the same spring while the contents it swaps stay where they are on screen.
  • Content: moving from one item to another, the content changes in place, like the Menubar's: the previous content leaves 24px away from where the next one comes from (moving to an item on its right, to the left) while it fades out (220ms, a soft ease-out), and the next one comes in from 24px that way on a spring (stiffness 450, damping 43) and fades in behind it (260ms, on a slow start); the Viewport clips them. A copy of the previous content (aria-hidden, inert) leaves in its place. Going back and forth faster than they settle, each content shows once: what's still leaving goes on from where it is, and back to an item whose content is still leaving, it comes back from there (where it is, as it shows) instead of coming in again. Without a Viewport, each Content fades and scales under its item like the Viewport does.
  • Clicking a trigger: a mouse reaches a trigger before it clicks it, and that opens its item, or switches the menu to it. A click within 500ms of that keeps the menu open, like Base UI's "patient click": the menu the pointer has just opened doesn't close under the click. Animate UI handles trigger clicks itself, since reka-ui ignores any click for 300ms after the pointer moves. Later clicks, the next click, clicks on an item a click opened, triggers that are links (as-child: keeping it open would cancel their navigation), and the keyboard toggle it closed as usual.
  • The highlights: the triggers, and the links beside them (navigationMenuTriggerStyle), share one highlight, like a Menubar's: a trigger hovered, focused or open wears its own background (hover:bg-accent, focus:bg-accent, data-[state=open]:bg-accent/50…, read from your styles and cleared on the trigger), and that background glides from trigger to trigger on the highlight's spring (stiffness 500, damping 40). It fades in (200ms, on CSS's ease) when it appears from nothing and fades out where it is the same way when no trigger is lit anymore; lit again meanwhile, it comes back from there. The links of each Content share their own the same way. The List and each Content become their own stacking context (isolation: isolate), so a highlight goes under the text and above the background.
  • Indicator follows the active trigger on the highlight's spring (stiffness 500, damping 40): Animate UI animates --reka-navigation-menu-indicator-position and --reka-navigation-menu-indicator-size, so classes reading them glide, fading in on it and out 200ms after the menu closes.
  • The highlight's color follows the trigger's state on the same curve: open (data-[state=open]:bg-accent/50), then hovered (hover:bg-accent). Without a Viewport, Contents show inside the List, which is its own stacking context for the highlight: a z-index on a Content then orders it among what's inside the List only.
  • Keyboard navigation between links and triggers moves the focus, and the highlight with it wherever your styles light a focused trigger or link (focus:bg-accent).
  • With force-mount, closed parts stay in the DOM: they play their exit, then are hidden (visibility: hidden).
  • An item open from the start (default-value) shows with the Viewport's entrance on page load.
  • Under reduced motion, everything only fades and sizes change at once: moving between items, each content fades in place; the highlights jump from trigger to trigger and from link to link.

API Reference

Every part takes exactly the props of reka-ui's Navigation Menu. Animate UI adds the animation props below.

reka-ui props only. Triggers and links take part in the highlights; they need nothing more.

reka-ui props, plus:

PropTypeDefault
transition?
Transition{ duration: 0.22, ease: [0.215, 0.61, 0.355, 1] } (the resizing: { type: 'spring', stiffness: 660, damping: 52 })
exit-transition?
Transitiontransition

reka-ui props, plus:

PropTypeDefault
transition?
Transition{ duration: 0.22, ease: [0.215, 0.61, 0.355, 1] }
exit-transition?
Transitiontransition

reka-ui props, plus:

PropTypeDefault
transition?
Transition{ type: 'spring', stiffness: 500, damping: 40 }
exit-transition?
Transition{ type: 'spring', stiffness: 500, damping: 40, delay: 0.2 }

Built by Skyleen. The source code is available on GitHub.

Last updated: 10/23/2026