---
name: navigation-menu
title: Navigation Menu
url: https://ui.frontic.com/components/ui/navigation-menu.md
tier: ui
install: npx @frontic/ui add navigation-menu
files:
  - components/ui/navigation-menu/NavigationMenu.vue
  - components/ui/navigation-menu/NavigationMenuContent.vue
  - components/ui/navigation-menu/NavigationMenuIndicator.vue
  - components/ui/navigation-menu/NavigationMenuItem.vue
  - components/ui/navigation-menu/NavigationMenuLink.vue
  - components/ui/navigation-menu/NavigationMenuList.vue
  - components/ui/navigation-menu/NavigationMenuTrigger.vue
  - components/ui/navigation-menu/NavigationMenuViewport.vue
  - components/ui/navigation-menu/context.ts
  - components/ui/navigation-menu/index.ts
dependencies:
  - "@lucide/vue"
  - "@vueuse/core"
  - class-variance-authority
  - reka-ui
---

# Navigation Menu

A row of site links where some open a panel of further links, shown in one shared panel below the row that resizes to each; set viewport to false to open each panel under its own trigger. For full-width flyouts with image tiles, use mega-menu.

## Install

```sh
npx @frontic/ui add navigation-menu
```

npm packages: `@lucide/vue`, `@vueuse/core`, `class-variance-authority`, `reka-ui`

## Examples

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

const categories = [
  { title: 'Jackets', description: 'Shells and insulated layers for the trail.' },
  { title: 'Backpacks', description: 'Day packs to multi-day haulers.' },
  { title: 'Goggles', description: 'Lenses for flat light and bright days.' },
  { title: 'Sale', description: 'Last season, reduced.' },
]
const help = ['Shipping', 'Returns', 'Size guide']
</script>

<template>
  <div class="min-h-72">
    <NavigationMenu>
      <NavigationMenuList>
        <NavigationMenuItem>
          <NavigationMenuTrigger>Shop</NavigationMenuTrigger>
          <NavigationMenuContent>
            <ul class="grid w-md grid-cols-2">
              <li v-for="category in categories" :key="category.title">
                <NavigationMenuLink href="#" class="flex-col items-start">
                  {{ category.title }}
                  <span class="text-muted-foreground">{{ category.description }}</span>
                </NavigationMenuLink>
              </li>
            </ul>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem>
          <NavigationMenuTrigger>Help</NavigationMenuTrigger>
          <NavigationMenuContent>
            <ul class="w-40">
              <li v-for="page in help" :key="page">
                <NavigationMenuLink href="#">{{ page }}</NavigationMenuLink>
              </li>
            </ul>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem>
          <NavigationMenuLink href="#" active>Journal</NavigationMenuLink>
        </NavigationMenuItem>
      </NavigationMenuList>
    </NavigationMenu>
  </div>
</template>
```

## Anatomy

```vue
<NavigationMenu>
  <NavigationMenuList>
    <NavigationMenuItem>
      <NavigationMenuTrigger />
      <NavigationMenuContent>
        <NavigationMenuLink />
      </NavigationMenuContent>
      <NavigationMenuLink />
    </NavigationMenuItem>
  </NavigationMenuList>
</NavigationMenu>
```

## API reference

### NavigationMenu

Groups all parts of the navigation menu. Renders a `<nav>`.

`data-slot="navigation-menu"` · `NavigationMenu.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `modelValue` | `string` |  | The controlled value of the menu item to activate. Can be used as `v-model`. |
| `defaultValue` | `string` |  | The value of the menu item that should be active when initially rendered.<br><br>Use when you do not need to control the value state. |
| `dir` | `Direction`: `'ltr' \| 'rtl'` |  | The reading direction of the combobox when applicable.<br><br> If omitted, inherits globally from `ConfigProvider` or assumes LTR (left-to-right) reading mode. |
| `orientation` | `Orientation`: `'vertical' \| 'horizontal'` | `'horizontal'` | The orientation of the menu. |
| `delayDuration` | `number` | `200` | The duration from when the pointer enters the trigger until the tooltip gets opened. |
| `skipDelayDuration` | `number` | `300` | How much time a user has to enter another trigger without incurring a delay again. |
| `disableClickTrigger` | `boolean` | `false` | If `true`, menu cannot be open by click on trigger |
| `disableHoverTrigger` | `boolean` | `false` | If `true`, menu cannot be open by hover on trigger |
| `disablePointerLeaveClose` | `boolean` | `false` | If `true`, menu will not close during pointer leave event |
| `unmountOnHide` | `boolean` | `true` | When `true`, the element will be unmounted on closed state. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'nav'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `viewport` | `boolean` | `true` | Shows open menu content in one shared panel below the bar; turn off to show each panel under its own trigger. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:modelValue` | `value: string` | Event handler called when the value changes. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ modelValue: string; }` | Holds the menu's list of items; gets the value of the open item as `modelValue`. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-viewport` |  | Carries the `viewport` prop. |

### NavigationMenuContent

Holds the links and content shown when its menu item is open.

`data-slot="navigation-menu-content"` · `NavigationMenuContent.vue`

Styling: `navigation-menu-content`, which extends [`overlay`](https://ui.frontic.com/DESIGN.md#overlay). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `forceMount` | `boolean` | `false` | Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| `disableOutsidePointerEvents` | `boolean` | `false` | When `true`, hover/focus/click interactions will be disabled on elements outside the `DismissableLayer`. Users will need to click twice on outside elements to interact with them: once to close the `DismissableLayer`, and again to trigger the element. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Event | Payload | Description |
| --- | --- | --- |
| `escapeKeyDown` | `event: KeyboardEvent` | Event handler called when the escape key is down. Can be prevented. |
| `pointerDownOutside` | `event: PointerDownOutsideEvent` | Event handler called when a `pointerdown` event happens outside of the `DismissableLayer`. Can be prevented. |
| `focusOutside` | `event: FocusOutsideEvent` | Event handler called when the focus moves outside of the `DismissableLayer`. Can be prevented. |
| `interactOutside` | `event: PointerDownOutsideEvent \| FocusOutsideEvent` | Event handler called when an interaction happens outside the `DismissableLayer`. Specifically, when a `pointerdown` event happens outside or focus moves outside of it. Can be prevented. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-dismissable-layer` |  | Marks a layer that closes on Escape or a click outside it. |
| `data-motion` | `'from-start' \| 'from-end' \| 'to-start' \| 'to-end'` | The direction the content animates in from or out to as the active item changes. |
| `data-orientation` | `'horizontal' \| 'vertical'` | The direction the part is laid out in. |
| `data-state` | `'open' \| 'closed'` | The state the part is in, one value per state. |

### NavigationMenuIndicator

Points to the trigger of the currently open navigation menu item.

`data-slot="navigation-menu-indicator"` · `NavigationMenuIndicator.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `forceMount` | `boolean` | `false` | Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-orientation` | `'horizontal' \| 'vertical'` | The direction the part is laid out in. |
| `data-state` | `'visible' \| 'hidden'` | The state the part is in, one value per state. |

| CSS variable | Description |
| --- | --- |
| `--reka-navigation-menu-indicator-position` | The active trigger's offset along the list, in pixels. |
| `--reka-navigation-menu-indicator-size` | The active trigger's width, so the indicator can match it. |

### NavigationMenuItem

Groups a trigger with the content it opens, or holds a single link. Renders a `<li>`.

`data-slot="navigation-menu-item"` · `NavigationMenuItem.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` |  | A unique value that associates the item with an active value when the navigation menu is controlled.<br><br> This prop is managed automatically when uncontrolled. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'li'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-menu-item` |  | Marks the item. |

### NavigationMenuLink

Links to a page from the navigation menu. Renders a `<a>`.

`data-slot="navigation-menu-link"` · `NavigationMenuLink.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `active` | `boolean` | `false` | Used to identify the link as the currently active page. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'a'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Event | Payload | Description |
| --- | --- | --- |
| `select` | `payload: CustomEvent<{ originalEvent: Event; }>` | Event handler called when the user selects a link (via mouse or keyboard). Calling `event.preventDefault` in this handler will prevent the navigation menu from closing when selecting that link. |

### NavigationMenuList

Lists the top-level items of the navigation menu. Renders a `<ul>`.

`data-slot="navigation-menu-list"` · `NavigationMenuList.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'ul'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-orientation` | `'horizontal' \| 'vertical'` | The direction the part is laid out in. |

### NavigationMenuTrigger

Opens and closes the content of its navigation menu item. Renders a `<button>`.

`data-slot="navigation-menu-trigger"` · `NavigationMenuTrigger.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | `boolean` | `false` | When `true`, prevents the user from interacting with item |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-disabled` |  | Present while the part is disabled. |
| `data-navigation-menu-trigger` |  | Marks the trigger. |
| `data-state` | `'open' \| 'closed'` | The state the part is in, one value per state. |

### NavigationMenuViewport

Shows the content of the open navigation menu item.

`data-slot="navigation-menu-viewport"` · `NavigationMenuViewport.vue`

Styling: `navigation-menu-viewport`, which extends [`overlay`](https://ui.frontic.com/DESIGN.md#overlay). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `forceMount` | `boolean` | `false` | Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| `align` | `'center' \| 'start' \| 'end'` | `'center'` | Placement of the viewport for css variables `(--reka-navigation-menu-viewport-left, --reka-navigation-menu-viewport-top)`. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-orientation` | `'horizontal' \| 'vertical'` | The direction the part is laid out in. |
| `data-state` | `'open' \| 'closed'` | The state the part is in, one value per state. |

| CSS variable | Description |
| --- | --- |
| `--reka-navigation-menu-viewport-height` | The open content's height, so the viewport can animate to it. |
| `--reka-navigation-menu-viewport-left` | The open content's left offset, in pixels. |
| `--reka-navigation-menu-viewport-top` | The open content's top offset, in pixels. |
| `--reka-navigation-menu-viewport-width` | The open content's width, so the viewport can animate to it. |

## Styling

| Part | Recipe |
| --- | --- |
| `list` | [`navigation-menu-list`](https://ui.frontic.com/DESIGN.md#navigation-menu-list) |
| `indicator` | [`navigation-menu-indicator`](https://ui.frontic.com/DESIGN.md#navigation-menu-indicator) |
| `default` | [`navigation-menu`](https://ui.frontic.com/DESIGN.md#navigation-menu) |
| `trigger` | [`navigation-menu-trigger`](https://ui.frontic.com/DESIGN.md#navigation-menu-trigger) |
| `content` | [`navigation-menu-content`](https://ui.frontic.com/DESIGN.md#navigation-menu-content) |
| `link` | [`navigation-menu-link`](https://ui.frontic.com/DESIGN.md#navigation-menu-link) |
| `indicator-arrow` | [`navigation-menu-indicator-arrow`](https://ui.frontic.com/DESIGN.md#navigation-menu-indicator-arrow) |
| `focus-ring` | [`focus-ring`](https://ui.frontic.com/DESIGN.md#focus-ring) |
| `disabled` | [`disabled`](https://ui.frontic.com/DESIGN.md#disabled) |
| `viewport` | [`navigation-menu-viewport`](https://ui.frontic.com/DESIGN.md#navigation-menu-viewport) |

States with a look of their own: hover, focus and disabled.

## Source

`components/ui/navigation-menu/NavigationMenu.vue`

```vue
<script lang="ts">
/** Groups all parts of the navigation menu. */
export default {}
</script>

<script setup lang="ts">
import type { NavigationMenuRootEmits, NavigationMenuRootProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { NavigationMenuRoot, useForwardPropsEmits } from 'reka-ui'
import NavigationMenuViewport from './NavigationMenuViewport.vue'

const props = withDefaults(
  defineProps<
    NavigationMenuRootProps & {
      class?: HTMLAttributes['class']
      /** Shows open menu content in one shared panel below the bar; turn off to show each panel under its own trigger. */
      viewport?: boolean
    }
  >(),
  {
    viewport: true,
  },
)
const emits = defineEmits<NavigationMenuRootEmits>()

const delegatedProps = reactiveOmit(props, 'class', 'viewport')
const forwarded = useForwardPropsEmits(delegatedProps, emits)

defineSlots<{
  /** Holds the menu's list of items; gets the value of the open item as `modelValue`. */
  default?: (props: { modelValue: string }) => unknown
}>()
</script>

<template>
  <NavigationMenuRoot v-slot="slotProps" data-slot="navigation-menu" :data-viewport="viewport" v-bind="forwarded" :class="['fui-navigation-menu group/navigation-menu relative', props.class]">
    <slot v-bind="slotProps" />
    <NavigationMenuViewport v-if="viewport" />
  </NavigationMenuRoot>
</template>
```

`components/ui/navigation-menu/NavigationMenuContent.vue`

```vue
<script lang="ts">
/** Holds the links and content shown when its menu item is open. */
export default {}
</script>

<script setup lang="ts">
import type { NavigationMenuContentEmits, NavigationMenuContentProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { cva } from 'class-variance-authority'
import { injectNavigationMenuContext, NavigationMenuContent, useForwardPropsEmits } from 'reka-ui'
import { provide } from 'vue'
import { NAVIGATION_MENU_PANEL } from './context'

const props = defineProps<NavigationMenuContentProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<NavigationMenuContentEmits>()

const delegatedProps = reactiveOmit(props, 'class')

const forwarded = useForwardPropsEmits(delegatedProps, emits)
const menu = injectNavigationMenuContext()
provide(NAVIGATION_MENU_PANEL, true)
// Without a viewport the content draws its own surface.
const content = cva(
  'fui-navigation-menu-content absolute inset-s-0 data-motion-from:animate-in data-motion-to:animate-out data-motion-from:fade-in data-motion-to:fade-out data-motion-from-end:slide-in-from-end-52 data-motion-from-start:slide-in-from-start-52 data-motion-to-end:slide-out-to-end-52 data-motion-to-start:slide-out-to-start-52',
  {
    variants: { surface: { shared: 'fui-navigation-menu-content-shared', own: 'fui-navigation-menu-content-own' } },
    defaultVariants: { surface: 'shared' },
  },
)
</script>

<template>
  <NavigationMenuContent
    data-slot="navigation-menu-content"
    v-bind="forwarded"
    :class="[
      content({ surface: menu.viewport.value ? 'shared' : 'own' }),
      menu.viewport.value
        ? 'top-0'
        : 'top-full data-open:animate-in data-closed:animate-out data-open:zoom-in-95 data-closed:zoom-out-95 data-open:fade-in-0 data-closed:fade-out-0 duration-300 overflow-hidden',
      props.class,
    ]"
  >
    <slot />
  </NavigationMenuContent>
</template>
```

`components/ui/navigation-menu/NavigationMenuIndicator.vue`

```vue
<script lang="ts">
/** Points to the trigger of the currently open navigation menu item. */
export default {}
</script>

<script setup lang="ts">
import type { NavigationMenuIndicatorProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { NavigationMenuIndicator, useForwardProps } from 'reka-ui'

const props = defineProps<NavigationMenuIndicatorProps & { class?: HTMLAttributes['class'] }>()

const delegatedProps = reactiveOmit(props, 'class')

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
  <NavigationMenuIndicator
    data-slot="navigation-menu-indicator"
    v-bind="forwardedProps"
    :class="[
      'fui-navigation-menu-indicator top-full z-1 data-state-visible:animate-in data-state-hidden:animate-out data-state-visible:fade-in data-state-hidden:fade-out overflow-hidden',
      props.class,
    ]"
  >
    <div class="fui-navigation-menu-indicator-arrow relative top-3/5 rotate-45" />
  </NavigationMenuIndicator>
</template>
```

`components/ui/navigation-menu/NavigationMenuItem.vue`

```vue
<script lang="ts">
/** Groups a trigger with the content it opens, or holds a single link. */
export default {}
</script>

<script setup lang="ts">
import type { NavigationMenuItemProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { NavigationMenuItem, useForwardProps } from 'reka-ui'

const props = defineProps<NavigationMenuItemProps & { class?: HTMLAttributes['class'] }>()

const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardProps(delegatedProps)
</script>

<template>
  <NavigationMenuItem data-slot="navigation-menu-item" v-bind="forwarded" :class="['relative', props.class]">
    <slot />
  </NavigationMenuItem>
</template>
```

`components/ui/navigation-menu/NavigationMenuLink.vue`

```vue
<script lang="ts">
/** Links to a page from the navigation menu. */
export default {}
</script>

<script setup lang="ts">
import type { NavigationMenuLinkEmits, NavigationMenuLinkProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { cva } from 'class-variance-authority'
import { NavigationMenuLink, useForwardPropsEmits } from 'reka-ui'
import { inject } from 'vue'
import { NAVIGATION_MENU_PANEL } from './context'

const props = defineProps<NavigationMenuLinkProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<NavigationMenuLinkEmits>()

const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)
// A link in the bar sits beside the triggers and takes their size.
const place = inject(NAVIGATION_MENU_PANEL, undefined) ? 'panel' : 'bar'
const link = cva('fui-navigation-menu-link outline-none transition-all focus-visible:fui-focus-ring **:data-icon:shrink-0', {
  variants: { place: { panel: 'fui-navigation-menu-link-panel', bar: 'fui-navigation-menu-link-bar' } },
  defaultVariants: { place: 'panel' },
})
</script>

<template>
  <NavigationMenuLink data-slot="navigation-menu-link" v-bind="forwarded" :class="[link({ place }), props.class]">
    <slot />
  </NavigationMenuLink>
</template>
```

`components/ui/navigation-menu/NavigationMenuList.vue`

```vue
<script lang="ts">
/** Lists the top-level items of the navigation menu. */
export default {}
</script>

<script setup lang="ts">
import type { NavigationMenuListProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { NavigationMenuList, useForwardProps } from 'reka-ui'

const props = defineProps<NavigationMenuListProps & { class?: HTMLAttributes['class'] }>()

const delegatedProps = reactiveOmit(props, 'class')

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
  <NavigationMenuList data-slot="navigation-menu-list" v-bind="forwardedProps" :class="['fui-navigation-menu-list group list-none', props.class]">
    <slot />
  </NavigationMenuList>
</template>
```

`components/ui/navigation-menu/NavigationMenuTrigger.vue`

```vue
<script lang="ts">
/** Opens and closes the content of its navigation menu item. */
export default {}
</script>

<script setup lang="ts">
import { ChevronDownIcon } from '@lucide/vue'

import type { NavigationMenuTriggerProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { NavigationMenuTrigger, useForwardProps } from 'reka-ui'

const props = defineProps<NavigationMenuTriggerProps & { class?: HTMLAttributes['class'] }>()

const delegatedProps = reactiveOmit(props, 'class')

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
  <NavigationMenuTrigger
    data-slot="navigation-menu-trigger"
    v-bind="forwardedProps"
    :class="['fui-navigation-menu-trigger group/navigation-menu-trigger outline-none transition-all focus-visible:fui-focus-ring disabled:pointer-events-none disabled:fui-disabled', props.class]"
  >
    <slot />
    <ChevronDownIcon data-icon class="relative top-px ms-1 transition duration-300 group-aria-expanded/navigation-menu-trigger:rotate-180" aria-hidden="true" />
  </NavigationMenuTrigger>
</template>
```

`components/ui/navigation-menu/NavigationMenuViewport.vue`

```vue
<script lang="ts">
/** Shows the content of the open navigation menu item. */
export default {}
</script>

<script setup lang="ts">
import type { NavigationMenuViewportProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { NavigationMenuViewport, useForwardProps } from 'reka-ui'

const props = defineProps<NavigationMenuViewportProps & { class?: HTMLAttributes['class'] }>()

const delegatedProps = reactiveOmit(props, 'class')

const forwardedProps = useForwardProps(delegatedProps)
</script>

<template>
  <div class="absolute top-full inset-s-0 isolate z-50 flex justify-center">
    <NavigationMenuViewport
      data-slot="navigation-menu-viewport"
      v-bind="forwardedProps"
      :class="[
        'fui-navigation-menu-viewport relative origin-top data-open:animate-in data-closed:animate-out data-open:zoom-in-90 data-closed:zoom-out-90 transition-all duration-200 ease-out overflow-hidden',
        props.class,
      ]"
    />
  </div>
</template>
```

`components/ui/navigation-menu/context.ts`

```ts
import type { InjectionKey } from 'vue'

export const NAVIGATION_MENU_PANEL: InjectionKey<true> = Symbol('navigation-menu-panel')
```
