Navigation Menu

Displays a collection of links for navigating websites.

Examples

Basic

PropDefaultTypeDescription
items[]TThe items to display in the navigation-menu.
delayDuration200numberThe duration from when the pointer enters the trigger until the tooltip gets opened.
defaultValue-stringThe value of the menu item that should be active when initially rendered.
disableClickTriggerfalsebooleanIf true, menu cannot be open by click on trigger.
disableHoverTriggerfalsebooleanIf true, menu cannot be open by hover on trigger.
disablePointerLeaveClose-booleanIf true, menu will not close during pointer leave event.
modelValue-stringThe controlled value of the menu item to activate. Can be used as v-model.
skipDelayDuration300numberHow much time a user has to enter another trigger without incurring a delay again.
unmountOnHidetruebooleanWhen true, the element will be unmounted on closed state.
Item PropDefaultTypeDescription
items[]T[]The items to display in the navigation-menu content.
slotundefinedstringThe slot name of the navigation-menu content.
Preview
Code

Indicator

PropDefaultTypeDescription
indicatorfalsebooleanSet the indicator that renders below the list,.
Preview
Code

Orientation

PropDefaultTypeDescription
orientationhorizontalhorizontal, verticalSet the orientation of the menu.
Preview
Code

Variant and Color

PropDefaultTypeDescription
navigation-menughost-white{variant}-{color}Set the navigation-menu variant and color.
navigation-menu-linkghost-white{variant}-{color}Set the navigation-menu link variant and color.
_navigationMenuTrigger.navigation-menughost-white{variant}-{color}Set the navigation-menu trigger variant and color via _navigationMenuTrigger.
_navigationMenuLink.navigation-menu-linkghost-white{variant}-{color}Set the navigation-menu link variant and color via _navigationMenuLink.
Preview
Code

Size

Adjust the navigation-menu size without limits. Use breakpoints (e.g., sm:sm, xs:lg) for responsive sizes or states (e.g., hover:lg, focus:3xl) for state-based sizes.

PropDefaultTypeDescription
sizesmstringAdjusts the overall size of the navigation-menu component.
_navigationMenuItem.sizesmstringCustomizes the size of each item within the navigation-menu.
_navigationMenuTrigger.sizesmstringModifies the size of the navigation-menu trigger element.
_navigationMenuLink.sizesmstringAdjusts the size of the navigation-menu link.
Preview
Code

Slots

NamePropsDescription
defaultitemsThe default slot, overrides everything.
listitemsThe list slot.
triggeritem, index, modelValueThe trigger slot.
itemitem, activeThe item static slot.
#{{ item.slot }}item, activeThe item dynamic slot.
item-contentitems, itemThe content static slot.
#{{ item.slot }}-contentitems, itemThe content dynamic slot.
Preview
Code

The default slot allows you to completely override the NavigationMenu structure, following the same pattern used in shadcn/ui.

Preview
Code

Presets

shortcuts/navigation-menu.ts
type NavigationMenuPrefix = 'navigation-menu'

export const staticNavigationMenu: Record<`${NavigationMenuPrefix}-${string}` | NavigationMenuPrefix, string> = {
  // configurations
  'navigation-menu': 'relative z-10 flex max-w-max flex-1 items-center justify-center',
  'navigation-menu-disabled': 'n-disabled',

  // components
  'navigation-menu-indicator': 'absolute data-[state=hidden]:opacity-0 duration-200 top-full data-[state=visible]:animate-fadeIn data-[state=hidden]:animate-fadeOut w-[--reka-navigation-menu-indicator-size] translate-x-[--reka-navigation-menu-indicator-position] mt--3px z-100 flex h-10px items-end justify-center overflow-hidden transition-all',
  'navigation-menu-indicator-arrow': 'relative top-70% h-12px w-12px rotate-45deg border bg-base',
  'navigation-menu-content': 'left-0 top-0 w-full md:absolute md:w-auto',
  'navigation-menu-viewport': 'origin-top-center relative mt-1.5 h-[--reka-navigation-menu-viewport-height] w-full overflow-hidden rounded-md border bg-popover text-popover shadow md:w-[--reka-navigation-menu-viewport-width]',

  'navigation-menu-link': '',
  'navigation-menu-trigger': '',
  'navigation-menu-trigger-trailing': 'size-5 transform shrink-0 group-data-[state=open]:rotate-180 transition-transform duration-200',
  'navigation-menu-trigger-trailing-icon': 'i-lucide-chevron-down',

  'navigation-menu-content-item': 'flex items-start select-none rounded-md p-3 h-unset leading-none no-underline outline-none transition-colors whitespace-normal justify-start',
  'navigation-menu-content-list': 'grid w-400px gap-3 p-4 md:grid-cols-2 lg:w-600px md:w-500px',
  'navigation-menu-content-item-wrapper': 'flex flex-col items-start gap-1',
  'navigation-menu-content-item-label': 'font-semibold leading-none',
  'navigation-menu-content-item-description': 'line-clamp-2 text-left text-muted leading-5',

  'navigation-menu-list-horizontal': 'flex flex-1 list-none items-center justify-center gap-x-1',
  'navigation-menu-list-vertical': 'max-w-none list-none flex-col items-start gap-1 space-x-0',

  'navigation-menu-viewport-wrapper': '',
  'navigation-menu-viewport-wrapper-horizontal': 'absolute left-0 top-full flex justify-center',
  'navigation-menu-viewport-wrapper-vertical': 'absolute top-0 left-full ml-1.5',
}

export const dynamicNavigationMenu = [
  [/^navigation-menu-([^-]+)-([^-]+)$/, ([, v = 'ghost', c = 'white']) => `btn-${v}-${c}`],
  [/^navigation-menu-link-([^-]+)-([^-]+)$/, ([, v = 'ghost', c = 'white']) => `btn-${v}-${c}`],
]

export const navigationMenu = [
  ...dynamicNavigationMenu,
  staticNavigationMenu,
]

Props

types/navigation-menu.ts
import type {
  NavigationMenuContentProps,
  NavigationMenuIndicatorProps,
  NavigationMenuItemProps,
  NavigationMenuLinkProps,
  NavigationMenuListProps,
  NavigationMenuRootProps,
  NavigationMenuSubProps,
  NavigationMenuTriggerProps,
  NavigationMenuViewportProps,
} from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { NButtonProps } from './button'

interface BaseExtensions {
  /** CSS class for the component */
  class?: HTMLAttributes['class']
  /** Size of the component */
  size?: HTMLAttributes['class']
}

export interface NNavigationMenuProps<T> extends Omit<NavigationMenuRootProps, 'class'>, Pick<NNavigationMenuTriggerProps, 'navigationMenu' | 'disabled'>,
  Pick<NNavigationMenuLinkProps, 'navigationMenuLink'>, BaseExtensions {
  /**
   * The array of items that is passed to the navigation menu.
   *
   * @default []
   */
  items?: T
  /** Whether to show the indicator or not */
  indicator?: boolean

  /** Props for the navigation menu trigger */
  _navigationMenuTrigger?: Partial<NNavigationMenuTriggerProps>
  /** Props for the navigation menu content */
  _navigationMenuContent?: Partial<NNavigationMenuContentProps>
  /** Props for the navigation menu item */
  _navigationMenuItem?: Partial<NNavigationMenuItemProps>
  /** Props for the navigation menu viewport */
  _navigationMenuViewport?: Partial<NNavigationMenuViewportProps>
  /** Props for the navigation menu list */
  _navigationMenuList?: Partial<NNavigationMenuListProps>
  /** Props for the navigation menu list item */
  _navigationMenuListItem?: Partial<NNavigationMenuListItemProps>
  /** Props for the navigation menu link */
  _navigationMenuLink?: Partial<NNavigationMenuLinkProps>
  /** Props for the navigation menu indicator */
  _navigationMenuIndicator?: Partial<NNavigationMenuIndicatorProps>

  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/navigation-menu.ts
   */
  una?: NNavigationMenuUnaProps
}

export interface NNavigationMenuTriggerProps extends NavigationMenuTriggerProps, Omit<NButtonProps, 'una'> {
  /**
   * Allows you to add `UnaUI` button preset properties,
   * Think of it as a shortcut for adding options or variants to the preset if available.
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/navigation-menu.ts
   * @example
   * navigation-menu="solid-indigo"
   */
  navigationMenu?: string
  /** Additional properties for the una component */
  una?: Pick<NNavigationMenuUnaProps, 'navigationMenuTrigger' | 'navigationMenuDefaultVariant'> & NButtonProps['una']
}

export interface NNavigationMenuContentProps extends NavigationMenuContentProps, BaseExtensions {
  /** Additional properties for the una component */
  una?: NNavigationMenuUnaProps['navigationMenuContent']
}

export interface NNavigationMenuItemProps extends NavigationMenuItemProps, Omit<NNavigationMenuTriggerProps, 'una'>,
  Pick<NNavigationMenuLinkProps, 'active' | 'onSelect'>, Pick<NNavigationMenuProps<NNavigationMenuItemProps[]>, '_navigationMenuLink' | '_navigationMenuTrigger'> {
  /** Slot of the navigation menu item */
  slot?: string
  /** The array of links that is passed to the navigation menu items. */
  items?: NNavigationMenuLinkProps[]
  /** Additional properties for the una component */
  una?: Pick<NNavigationMenuUnaProps, 'navigationMenuTrigger' | 'navigationMenuContent'>
}

export interface NNavigationMenuIndicatorProps extends NavigationMenuIndicatorProps, BaseExtensions {
  /** Additional properties for the una component */
  una?: NNavigationMenuUnaProps['navigationMenuIndicator']
}

export interface NNavigationMenuLinkProps extends NavigationMenuLinkProps, Omit<NButtonProps, 'size'>, BaseExtensions {
  /**
   * Allows you to add `UnaUI` button preset properties,
   * Think of it as a shortcut for adding options or variants to the preset if available.
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/navigation-menu.ts
   * @example
   * navigation-menu-link="ghost-gray"
   */
  navigationMenuLink?: string
  /** Event handler called when the link is clicked */
  onSelect?: (e: Event) => void
  /** Additional properties for the una component */
  una?: NNavigationMenuUnaProps['navigationMenuLink'] & NButtonProps['una']
}

export interface NNavigationMenuListProps extends NavigationMenuListProps, Pick<NavigationMenuRootProps, 'orientation'>, BaseExtensions {
  /** Additional properties for the una component */
  una?: NNavigationMenuUnaProps['navigationMenuList']
}

export interface NNavigationMenuListItemProps extends NNavigationMenuLinkProps {
  /** Description of the link. This is only used when `orientation` is `horizontal`. */
  description?: string
  /** Additional properties for the una component */
  una?: Pick<NNavigationMenuUnaProps, 'navigationMenuListItem' | 'navigationMenuContentItem' | 'navigationMenuContentItemWrapper' | 'navigationMenuContentItemLabel' | 'navigationMenuContentItemDescription'>
}

export interface NNavigationMenuSubProps extends NavigationMenuSubProps {}

export interface NNavigationMenuViewportProps extends NavigationMenuViewportProps, Pick<NavigationMenuRootProps, 'orientation'>, Pick<BaseExtensions, 'class'> {
  /** Additional properties for the una component */
  una?: Pick<NNavigationMenuUnaProps, 'navigationMenuViewport' | 'navigationMenuViewportWrapper'>
}

interface NNavigationMenuUnaProps {
  /** CSS class for the navigation menu */
  navigationMenu?: HTMLAttributes['class']
  /** CSS class for the navigation menu content */
  navigationMenuContent?: HTMLAttributes['class']
  /** CSS class for the navigation menu content item */
  navigationMenuContentItem?: HTMLAttributes['class']
  /** CSS class for the navigation menu content item wrapper */
  navigationMenuContentItemWrapper?: HTMLAttributes['class']
  /** CSS class for the navigation menu content item label */
  navigationMenuContentItemLabel?: HTMLAttributes['class']
  /** CSS class for the navigation menu content item description */
  navigationMenuContentItemDescription?: HTMLAttributes['class']
  /** CSS class for the navigation menu trigger */
  navigationMenuTrigger?: HTMLAttributes['class']
  /** CSS class for the navigation menu trigger default variant */
  navigationMenuDefaultVariant?: HTMLAttributes['class']
  /** CSS class for the navigation menu list */
  navigationMenuList?: HTMLAttributes['class']
  /** CSS class for the navigation menu list item */
  navigationMenuListItem?: HTMLAttributes['class']
  /** CSS class for the navigation menu item */
  navigationMenuItem?: HTMLAttributes['class']
  /** CSS class for the navigation menu link */
  navigationMenuLink?: HTMLAttributes['class']
  /** CSS class for the navigation menu indicator */
  navigationMenuIndicator?: HTMLAttributes['class']
  /** CSS class for the navigation menu indicator arrow */
  navigationMenuIndicatorArrow?: HTMLAttributes['class']
  /** CSS class for the navigation menu viewport */
  navigationMenuViewport?: HTMLAttributes['class']
  /** CSS class for the navigation menu viewport wrapper */
  navigationMenuViewportWrapper?: HTMLAttributes['class']
}

Components

NavigationMenu.vue
NavigationMenuTrigger.vue
NavigationMenuItem.vue
NavigationMenuContent.vue
NavigationMenuIndicator.vue
NavigationMenuViewport.vue
NavigationMenuLink.vue
NavigationMenuList.vue
NavigationMenuContentItem.vue
<script setup lang="ts" generic="T extends U[], U extends NNavigationMenuItemProps">
import type { NavigationMenuRootEmits } from 'reka-ui'
import type { NNavigationMenuItemProps, NNavigationMenuProps } from '../../types'
import { createReusableTemplate, reactiveOmit } from '@vueuse/core'
import { NavigationMenuRoot, useForwardPropsEmits } from 'reka-ui'
import { cn, omitProps } from '../../utils'
import NavigationMenuContent from './NavigationMenuContent.vue'
import NavigationMenuContentItem from './NavigationMenuContentItem.vue'
import NavigationMenuIndicator from './NavigationMenuIndicator.vue'
import NavigationMenuItem from './NavigationMenuItem.vue'
import NavigationMenuLink from './NavigationMenuLink.vue'
import NavigationMenuList from './NavigationMenuList.vue'
import NavigationMenuTrigger from './NavigationMenuTrigger.vue'
import NavigationMenuViewport from './NavigationMenuViewport.vue'

defineOptions({
  inheritAttrs: false,
})

const props = withDefaults(defineProps<NNavigationMenuProps<T>>(), {
  orientation: 'horizontal',
  unmountOnHide: true,
})
const emits = defineEmits<NavigationMenuRootEmits>()

const rootProps = reactiveOmit(props, ['navigationMenu', 'navigationMenuLink', 'una', 'items'])
const forwarded = useForwardPropsEmits(rootProps, emits)

const [DefineLinkTemplate, ReuseLinkTemplate] = createReusableTemplate()
</script>

<template>
  <NavigationMenuRoot
    v-slot="{ modelValue }"
    v-bind="forwarded"
    :class="cn(
      'navigation-menu',
      props.una?.navigationMenu,
      props.class,
    )"
  >
    <slot :items="items">
      <DefineLinkTemplate v-slot="{ link, isList }">
        <component
          :is="isList ? NavigationMenuContentItem : NavigationMenuLink"
          :size
          :una
          :disabled="link.disabled"
          v-bind="{ ...link, ...props._navigationMenuLink }"
        />
      </DefineLinkTemplate>

      <NavigationMenuList
        v-bind="props._navigationMenuList"
        :una
        :orientation
      >
        <slot name="list" :items="items">
          <template
            v-for="item, idx in items"
            :key="item"
          >
            <NavigationMenuItem
              v-if="item.items && item.items?.length > 0"
              v-bind="props._navigationMenuItem"
              :value="item.value"
              :una
            >
              <slot :name="item.slot || 'item'" :item="item" :active="item.active">
                <NavigationMenuTrigger
                  :size
                  :una
                  :navigation-menu="item?._navigationMenuTrigger?.navigationMenu ?? item.navigationMenu ?? navigationMenu"
                  :disabled="item?._navigationMenuTrigger?.disabled ?? item.disabled ?? disabled"
                  v-bind="{ ...omitProps(item, ['items']), ...props._navigationMenuTrigger, ...item?._navigationMenuTrigger }"
                >
                  <slot name="trigger" :model-value :item="item" :index="idx" />
                </NavigationMenuTrigger>

                <NavigationMenuContent
                  v-bind="props._navigationMenuContent"
                  :una
                >
                  <slot :name="item.slot ? `${item.slot}-content` : 'item-content'" :items="item.items" :item="item">
                    <ul class="navigation-menu-content-list">
                      <ReuseLinkTemplate
                        v-for="(child, childIndex) in item.items"
                        :key="childIndex"
                        :link="child"
                        :is-list="true"
                        :navigation-menu-link
                      />
                    </ul>
                  </slot>
                </NavigationMenuContent>
              </slot>
            </NavigationMenuItem>
            <NavigationMenuItem
              v-else
              v-bind="props._navigationMenuItem"
              :value="item.value"
              :una
            >
              <slot :name="item.slot || 'item'" :item="item">
                <ReuseLinkTemplate
                  :link="item"
                  :is-list="false"
                  :navigation-menu="item.navigationMenu ?? navigationMenu"
                />
              </slot>
            </NavigationMenuItem>
          </template>
        </slot>
        <NavigationMenuIndicator
          v-if="indicator"
          :una
        />
      </NavigationMenuList>
    </slot>

    <slot name="viewport">
      <NavigationMenuViewport
        v-bind="props._navigationMenuViewport"
        :orientation="props.orientation"
      />
    </slot>
  </NavigationMenuRoot>
</template>