Examples
Basic
| Prop | Default | Type | Description |
|---|---|---|---|
items | [] | T | The items to display in the navigation-menu. |
delayDuration | 200 | number | The duration from when the pointer enters the trigger until the tooltip gets opened. |
defaultValue | - | string | The value of the menu item that should be active when initially rendered. |
disableClickTrigger | false | boolean | If true, menu cannot be open by click on trigger. |
disableHoverTrigger | false | boolean | If true, menu cannot be open by hover on trigger. |
disablePointerLeaveClose | - | boolean | If true, menu will not close during pointer leave event. |
modelValue | - | string | The controlled value of the menu item to activate. Can be used as v-model. |
skipDelayDuration | 300 | number | How much time a user has to enter another trigger without incurring a delay again. |
unmountOnHide | true | boolean | When true, the element will be unmounted on closed state. |
| Item Prop | Default | Type | Description |
|---|---|---|---|
items | [] | T[] | The items to display in the navigation-menu content. |
slot | undefined | string | The slot name of the navigation-menu content. |
Preview
Code
<script setup lang="ts">
const items = [
{
label: 'Guide',
items: [
{
label: 'Introduction',
description: 'Una UI is an atomic UI Framework powered by the UNOCSS engine. It provides components and presets for creating stunning user interfaces with ease.',
to: '/getting-started/introduction',
active: true,
},
{
label: 'Installation',
description: 'How to install dependencies and structure your application with Una UI.',
to: '/getting-started/installation',
},
{
label: 'Themes',
description: 'Customize the theme of your application.',
to: '/api/themes',
},
{
label: 'Composables',
description: 'Composable functions that can be used in your application.',
to: '/api/composables',
},
],
},
{
label: 'Components',
items: [
{
label: 'Dropdown Menu',
description: 'Displays a menu to the user — such as a set of actions or functions — triggered by a button.',
to: '/components/dropdown-menu',
},
{
label: 'Sidebar',
description: 'A composable, themeable and customizable sidebar component.',
to: '/components/sidebar',
},
{
label: 'Table',
description: 'A powerful, responsive table and datagrids built using Tanstack',
to: '/components/table',
},
{
label: 'Tooltip',
description: 'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.',
to: '/components/tooltip',
},
],
},
{
label: 'GitHub',
leading: 'i-simple-icons-github',
to: 'https://github.com/una-ui/una-ui',
target: '_blank',
},
{
label: 'Help',
leading: 'i-lucide-circle-help',
disabled: true,
},
]
</script>
<template>
<NNavigationMenu
:items
/>
</template>
Read more in Reka Navigation Menu Root API.
Indicator
| Prop | Default | Type | Description |
|---|---|---|---|
indicator | false | boolean | Set the indicator that renders below the list,. |
Preview
Code
<script setup lang="ts">
const items = [
{
label: 'Guide',
leading: 'i-lucide:book-open',
items: [
{
label: 'Introduction',
description: 'Una UI is an atomic UI Framework powered by the UNOCSS engine. It provides components and presets for creating stunning user interfaces with ease.',
to: '/getting-started/introduction',
},
{
label: 'Installation',
description: 'How to install dependencies and structure your application with Una UI.',
to: '/getting-started/installation',
},
{
label: 'Themes',
description: 'Customize the theme of your application.',
to: '/api/themes',
},
{
label: 'Composables',
description: 'Composable functions that can be used in your application.',
to: '/api/composables',
},
],
},
{
label: 'Components',
leading: 'i-lucide:box',
items: [
{
label: 'Dropdown Menu',
description: 'Displays a menu to the user — such as a set of actions or functions — triggered by a button.',
to: '/components/dropdown-menu',
},
{
label: 'Sidebar',
description: 'A composable, themeable and customizable sidebar component.',
to: '/components/sidebar',
},
{
label: 'Table',
description: 'A powerful, responsive table and datagrids built using Tanstack',
to: '/components/table',
},
{
label: 'Tooltip',
description: 'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.',
to: '/components/tooltip',
},
],
},
{
label: 'GitHub',
leading: 'i-simple-icons-github',
to: 'https://github.com/una-ui/una-ui',
target: '_blank',
},
{
label: 'Help',
leading: 'i-lucide-circle-help',
disabled: true,
},
]
</script>
<template>
<NNavigationMenu :items indicator />
</template>
Read more in Reka Navigation Menu Indicator API.
Orientation
| Prop | Default | Type | Description |
|---|---|---|---|
orientation | horizontal | horizontal, vertical | Set the orientation of the menu. |
Preview
Code
<script setup lang="ts">
const items = [
{
label: 'Guide',
items: [
{
label: 'Introduction',
description: 'Una UI is an atomic UI Framework powered by the UNOCSS engine. It provides components and presets for creating stunning user interfaces with ease.',
to: '/getting-started/introduction',
},
{
label: 'Installation',
description: 'How to install dependencies and structure your application with Una UI.',
to: '/getting-started/installation',
},
{
label: 'Themes',
description: 'Customize the theme of your application.',
to: '/api/themes',
},
{
label: 'Composables',
description: 'Composable functions that can be used in your application.',
to: '/api/composables',
},
],
},
{
label: 'Components',
items: [
{
label: 'Dropdown Menu',
description: 'Displays a menu to the user — such as a set of actions or functions — triggered by a button.',
to: '/components/dropdown-menu',
},
{
label: 'Sidebar',
description: 'A composable, themeable and customizable sidebar component.',
to: '/components/sidebar',
},
{
label: 'Table',
description: 'A powerful, responsive table and datagrids built using Tanstack',
to: '/components/table',
},
{
label: 'Tooltip',
description: 'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.',
to: '/components/tooltip',
},
],
},
{
label: 'GitHub',
leading: 'i-simple-icons-github',
to: 'https://github.com/una-ui/una-ui',
target: '_blank',
},
{
label: 'Help',
leading: 'i-lucide-circle-help',
disabled: true,
},
]
const options = [
{ label: 'Horizontal', value: 'horizontal' },
{ label: 'Vertical', value: 'vertical' },
]
const radioValue = ref<'horizontal' | 'vertical'>('horizontal')
</script>
<template>
<div class="flex flex-col flex-wrap gap-4">
<NRadioGroup
v-model="radioValue"
radio-group="yellow"
orientation="horizontal"
:items="options"
/>
<NNavigationMenu
:items :orientation="radioValue"
indicator
/>
</div>
</template>
Variant and Color
| Prop | Default | Type | Description |
|---|---|---|---|
navigation-menu | ghost-white | {variant}-{color} | Set the navigation-menu variant and color. |
navigation-menu-link | ghost-white | {variant}-{color} | Set the navigation-menu link variant and color. |
_navigationMenuTrigger.navigation-menu | ghost-white | {variant}-{color} | Set the navigation-menu trigger variant and color via _navigationMenuTrigger. |
_navigationMenuLink.navigation-menu-link | ghost-white | {variant}-{color} | Set the navigation-menu link variant and color via _navigationMenuLink. |
Colors do not apply to the list item descriptions; use the una.navigationMenuContentItemDescription prop to customize them.
Preview
Code
<script setup lang="ts">
const items = [
{
label: 'Guide',
items: [
{
label: 'Introduction',
description: 'Una UI is an atomic UI Framework powered by the UNOCSS engine. It provides components and presets for creating stunning user interfaces with ease.',
to: '/getting-started/introduction',
},
{
label: 'Installation',
description: 'How to install dependencies and structure your application with Una UI.',
to: '/getting-started/installation',
},
{
label: 'Themes',
description: 'Customize the theme of your application.',
to: '/api/themes',
},
{
label: 'Composables',
description: 'Composable functions that can be used in your application.',
to: '/api/composables',
},
],
},
{
label: 'Components',
items: [
{
label: 'Dropdown Menu',
description: 'Displays a menu to the user — such as a set of actions or functions — triggered by a button.',
to: '/components/dropdown-menu',
},
{
label: 'Sidebar',
description: 'A composable, themeable and customizable sidebar component.',
to: '/components/sidebar',
},
{
label: 'Table',
description: 'A powerful, responsive table and datagrids built using Tanstack',
to: '/components/table',
},
{
label: 'Tooltip',
description: 'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.',
to: '/components/tooltip',
},
],
},
{
label: 'GitHub',
leading: 'i-simple-icons-github',
to: 'https://github.com/una-ui/una-ui',
target: '_blank',
},
{
label: 'Help',
leading: 'i-lucide-circle-help',
disabled: true,
},
]
</script>
<template>
<div class="flex flex-col gap-4">
<NNavigationMenu
:items
class="z-20"
navigation-menu="solid-gray"
navigation-menu-link="solid-white"
/>
<NNavigationMenu
:items
navigation-menu="~"
navigation-menu-link="solid-gray"
:una="{
navigationMenuContentItemDescription: 'text-primary group-hover:text-primary-active',
}"
/>
</div>
</template>
Read more in Button Variant and Color API.
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.
| Prop | Default | Type | Description |
|---|---|---|---|
size | sm | string | Adjusts the overall size of the navigation-menu component. |
_navigationMenuItem.size | sm | string | Customizes the size of each item within the navigation-menu. |
_navigationMenuTrigger.size | sm | string | Modifies the size of the navigation-menu trigger element. |
_navigationMenuLink.size | sm | string | Adjusts the size of the navigation-menu link. |
Preview
Code
<script setup lang="ts">
const items = [
{
label: 'Guide',
items: [
{
label: 'Introduction',
description: 'Una UI is an atomic UI Framework powered by the UNOCSS engine. It provides components and presets for creating stunning user interfaces with ease.',
to: '/getting-started/introduction',
},
{
label: 'Installation',
description: 'How to install dependencies and structure your application with Una UI.',
to: '/getting-started/installation',
},
{
label: 'Themes',
description: 'Customize the theme of your application.',
to: '/api/themes',
},
{
label: 'Composables',
description: 'Composable functions that can be used in your application.',
to: '/api/composables',
},
],
},
{
label: 'Components',
items: [
{
label: 'Dropdown Menu',
description: 'Displays a menu to the user — such as a set of actions or functions — triggered by a button.',
to: '/components/dropdown-menu',
},
{
label: 'Sidebar',
description: 'A composable, themeable and customizable sidebar component.',
to: '/components/sidebar',
},
{
label: 'Table',
description: 'A powerful, responsive table and datagrids built using Tanstack',
to: '/components/table',
},
{
label: 'Tooltip',
description: 'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.',
to: '/components/tooltip',
},
],
},
{
label: 'GitHub',
leading: 'i-simple-icons-github',
to: 'https://github.com/una-ui/una-ui',
target: '_blank',
},
{
label: 'Help',
leading: 'i-lucide-circle-help',
disabled: true,
},
]
</script>
<template>
<div class="flex flex-col gap-2">
<NNavigationMenu :items size="xs" class="z-50" />
<NNavigationMenu :items size="sm" class="z-40" />
<NNavigationMenu :items size="md" class="z-30" />
<NNavigationMenu :items size="lg" class="z-20" />
<NNavigationMenu :items size="xl" class="z-10" />
</div>
</template>
Slots
It is important that you can also use dynamic slots to customize individual parts of NavigationMenu. You also have the option for item and content to use the slot field in the object itself for further dynamic binding.
| Name | Props | Description |
|---|---|---|
default | items | The default slot, overrides everything. |
list | items | The list slot. |
trigger | item, index, modelValue | The trigger slot. |
item | item, active | The item static slot. |
#{{ item.slot }} | item, active | The item dynamic slot. |
item-content | items, item | The content static slot. |
#{{ item.slot }}-content | items, item | The content dynamic slot. |
Preview
Code
<script setup lang="ts">
const items = [
{
slot: 'features',
label: 'Features',
trailing: 'i-lucide-chevron-down',
items: [
{
label: 'Atomic Design',
description: 'Consistent, maintainable UI components',
to: '#features/atomic-design',
},
{
label: 'Responsive',
description: 'Works on all device sizes',
to: '#features/responsive',
},
{
label: 'Themeable',
description: 'Customize with your own themes',
to: '#features/themeable',
},
],
},
{
slot: 'components',
label: 'Components',
trailing: 'i-lucide-chevron-down',
items: [
{
label: 'Buttons',
description: 'Interactive button elements',
to: '#components/buttons',
},
{
label: 'Cards',
description: 'Content containers with various styles',
to: '#components/cards',
},
{
label: 'Forms',
description: 'Input components and validation',
to: '#components/forms',
},
],
},
{
slot: 'resources',
label: 'Resources',
trailing: 'i-lucide-chevron-down',
items: [
{
label: 'Documentation',
description: 'Guides and API references',
to: '#resources/docs',
},
{
label: 'Examples',
description: 'Sample code and demos',
to: '#resources/examples',
},
],
},
]
</script>
<template>
<div class="flex items-center justify-center p-2">
<NNavigationMenu
:items="items"
indicator
class="z-20"
:_navigation-menu-viewport="{
class: 'left-1/2 translate-x--1/2',
}"
>
<template #trigger="{ item }">
<div class="flex items-center gap-1.5 font-medium">
{{ item.label }}
</div>
</template>
<!-- Features content -->
<template #features-content="{ items: featureItems }">
<div class="w-[480px] overflow-hidden rounded-md p-5 shadow-md">
<div class="grid grid-cols-[1fr_1fr] gap-6">
<div>
<h3 class="mb-3 text-lg text-amber-600 font-medium dark:text-amber-400">
Features
</h3>
<NNavigationMenuContentItem
v-for="item in featureItems"
:key="item.label"
v-bind="item"
class="py-1.5"
:una="{
navigationMenuContentItemLabel: 'font-medium',
navigationMenuContentItemDescription: 'text-sm text-gray-600 dark:text-gray-400',
}"
/>
</div>
<img
src="https://images.unsplash.com/photo-1546900703-cf06143d1239?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80"
alt="Code editor"
class="h-full w-full rounded-md object-cover"
>
</div>
</div>
</template>
<!-- Components content -->
<template #components-content="{ items: componentItems }">
<div class="bg-card w-[500px] rounded-md p-6 shadow-md">
<div class="mb-5">
<h3 class="mb-2 text-xl text-primary font-semibold">
UI Components
</h3>
<p class="text-muted-foreground text-sm">
Explore our comprehensive collection of customizable, accessible components.
</p>
</div>
<div class="flex items-start gap-6">
<div class="w-44 space-y-4">
<img
src="https://images.unsplash.com/photo-1545235617-9465d2a55698?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80"
alt="UI Components"
class="h-36 w-full rounded-md object-cover shadow-sm"
>
<div class="rounded-md bg-primary/10 p-3">
<span class="mb-1 block text-xs text-primary font-medium">New Release</span>
<p class="text-muted-foreground text-xs">
Check out our latest components with improved accessibility and animations.
</p>
</div>
</div>
<div class="flex-1 space-y-3.5">
<h4 class="text-muted-foreground text-sm font-medium tracking-wide uppercase">
Core Components
</h4>
<div class="grid grid-cols-1 gap-2">
<NNavigationMenuContentItem
v-for="item in componentItems"
:key="item.label"
v-bind="item"
class="rounded-md p-2.5 transition-colors hover:bg-accent"
:una="{
navigationMenuContentItemLabel: 'font-medium text-base',
navigationMenuContentItemDescription: 'text-sm text-muted-foreground mt-0.5',
}"
/>
</div>
<div class="border-border mt-2 border-t pt-2">
<NLink class="mt-1 inline-flex items-center text-sm text-primary font-medium hover:underline" to="/components">
<span>View all components</span>
<span class="i-lucide-arrow-right ml-1 h-3.5 w-3.5" />
</NLink>
</div>
</div>
</div>
</div>
</template>
<!-- Resources content -->
<template #resources-content="{ items: resourceItems }">
<div class="w-[480px] overflow-hidden rounded-md shadow-md">
<div class="relative">
<img
src="https://images.unsplash.com/photo-1517694712202-14dd9538aa97?ixlib=rb-4.0.3&auto=format&fit=crop&w=1200&q=80"
alt="Coding workspace"
class="h-44 w-full object-cover object-center"
>
<div class="absolute inset-0 from-black/70 to-transparent bg-gradient-to-t" />
<div class="absolute bottom-0 left-0 w-full p-5">
<h3 class="mb-3 text-lg text-white font-medium">
Resources
</h3>
<div class="grid grid-cols-2 gap-3">
<NNavigationMenuContentItem
v-for="item in resourceItems"
:key="item.label"
v-bind="item"
class="rounded-md bg-foreground/10 p-3 backdrop-blur-sm hover:bg-foreground/20"
:una="{
navigationMenuContentItemLabel: 'font-medium text-white',
navigationMenuContentItemDescription: 'text-sm text-gray-200',
}"
/>
</div>
</div>
</div>
</div>
</template>
</NNavigationMenu>
</div>
</template>
The default slot allows you to completely override the
NavigationMenustructure, following the same pattern used in shadcn/ui.
Preview
Code
<script setup lang="ts">
const components: { title: string, to: string, description: string }[] = [
{
title: 'Alert Dialog',
to: '#docs/components/alert-dialog',
description:
'A modal dialog that interrupts the user with important content and expects a response.',
},
{
title: 'Hover Card',
to: '#docs/components/hover-card',
description:
'For sighted users to preview content available behind a link.',
},
{
title: 'Progress',
to: '#docs/components/progress',
description:
'Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.',
},
{
title: 'Scroll-area',
to: '#docs/components/scroll-area',
description: 'Visually or semantically separates content.',
},
{
title: 'Tabs',
to: '#docs/components/tabs',
description:
'A set of layered sections of content—known as tab panels—that are displayed one at a time.',
},
{
title: 'Tooltip',
to: '#docs/components/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>
<div class="flex items-center justify-center p-2">
<NNavigationMenu>
<NNavigationMenuList>
<NNavigationMenuIndicator />
<NNavigationMenuItem>
<NNavigationMenuTrigger>Getting started</NNavigationMenuTrigger>
<NNavigationMenuContent>
<ul class="grid gap-3 p-6 lg:grid-cols-[minmax(0,.75fr)_minmax(0,1fr)] lg:w-[500px] md:w-[400px]">
<li class="row-span-3">
<NNavigationMenuLink
as-child
>
<NLink
class="h-full w-full flex flex-col select-none justify-end rounded-md from-foreground/10 to-foreground/0 bg-gradient-to-b p-6 no-underline outline-none focus:shadow-md"
to="#"
>
<img src="https://www.reka-ui.com/logo.svg" class="h-6 w-6">
<div class="mb-2 mt-4 text-lg font-medium">
shadcn/ui test
</div>
<p class="text-sm text-muted leading-tight">
Beautifully designed components built with Radix UI and
Tailwind CSS.
</p>
</NLink>
</NNavigationMenuLink>
</li>
<li>
<NNavigationMenuLink as-child>
<NLink
to="#/docs/introduction"
class="block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors space-y-1 focus:bg-accent hover:bg-accent focus:text-accent hover:text-accent"
>
<div class="text-sm font-medium leading-none">
Introduction
</div>
<p class="line-clamp-2 text-sm text-muted leading-snug">
Re-usable components built using Radix UI and Tailwind CSS.
</p>
</NLink>
</NNavigationMenuLink>
</li>
<li>
<NNavigationMenuLink as-child>
<NLink
to="#/docs/installation"
class="block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors space-y-1 focus:bg-accent hover:bg-accent focus:text-accent hover:text-accent"
>
<div class="text-sm font-medium leading-none">
Installation
</div>
<p class="line-clamp-2 text-sm text-muted leading-snug">
How to install dependencies and structure your app.
</p>
</NLink>
</NNavigationMenuLink>
</li>
<li>
<NNavigationMenuLink as-child>
<NLink
to="#/docs/typography"
class="block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors space-y-1 focus:bg-accent hover:bg-accent focus:text-accent hover:text-accent"
>
<div class="text-sm font-medium leading-none">
Typography
</div>
<p class="line-clamp-2 text-sm text-muted leading-snug">
Styles for headings, paragraphs, lists...etc
</p>
</NLink>
</NNavigationMenuLink>
</li>
</ul>
</NNavigationMenuContent>
</NNavigationMenuItem>
<NNavigationMenuItem>
<NNavigationMenuTrigger>Components</NNavigationMenuTrigger>
<NNavigationMenuContent>
<ul class="grid w-[400px] gap-3 p-4 md:grid-cols-2 lg:w-[600px] md:w-[500px]">
<li v-for="component in components" :key="component.title">
<NNavigationMenuLink as-child>
<a
:to="component.to"
class="block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors space-y-1 focus:bg-accent hover:bg-accent focus:text-accent hover:text-accent"
>
<div class="text-sm font-medium leading-none">{{ component.title }}</div>
<p class="line-clamp-2 text-sm text-muted leading-snug">
{{ component.description }}
</p>
</a>
</NNavigationMenuLink>
</li>
</ul>
</NNavigationMenuContent>
</NNavigationMenuItem>
<NNavigationMenuItem>
<NNavigationMenuLink to="#/docs/introduction">
Documentation
</NNavigationMenuLink>
</NNavigationMenuItem>
</NNavigationMenuList>
</NNavigationMenu>
</div>
</template>
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 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?: Pick<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?: Pick<NNavigationMenuUnaProps, 'navigationMenuIndicator' | 'navigationMenuIndicatorArrow'>
}
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
/** Description of the link. This is only used when `orientation` is `horizontal`. */
description?: string
/** Event handler called when the link is clicked */
onSelect?: (e: Event) => void
/** Additional properties for the una component */
una?: Pick<NNavigationMenuUnaProps, 'navigationMenuLink'> & NButtonProps['una']
}
export interface NNavigationMenuListProps extends NavigationMenuListProps, Pick<NavigationMenuRootProps, 'orientation'>, BaseExtensions {
/** Additional properties for the una component */
una?: Pick<NNavigationMenuUnaProps, 'navigationMenuList'>
}
export interface NNavigationMenuListItemProps extends NNavigationMenuLinkProps {
/** Additional properties for the una component */
una?: NNavigationMenuLinkProps['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>
<script setup lang="ts">
import type { NNavigationMenuTriggerProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import {
NavigationMenuTrigger,
useForwardProps,
} from 'reka-ui'
import { cn } from '../../utils'
import Button from '../elements/Button.vue'
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps<NNavigationMenuTriggerProps>(), {
btn: '~',
navigationMenu: 'ghost-white',
trailing: 'navigation-menu-trigger-trailing-icon',
as: Button,
})
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<NavigationMenuTrigger
v-bind="!props.asChild ? {
...$attrs,
...forwardedProps,
class: cn(
'navigation-menu-trigger group',
props.una?.navigationMenuTrigger,
props.class,
),
navigationMenu: btn === '~' ? navigationMenu : undefined,
una: {
btnTrailing: cn('navigation-menu-trigger-trailing', forwardedProps.una?.btnTrailing),
...props.una,
},
} : {}"
:as-child="props.asChild"
:as="props.as"
:disabled
>
<slot />
</NavigationMenuTrigger>
</template>
<script setup lang="ts">
import type { NNavigationMenuItemProps } from '../../types'
import { NavigationMenuItem } from 'reka-ui'
const props = defineProps<NNavigationMenuItemProps>()
</script>
<template>
<NavigationMenuItem v-bind="props">
<slot />
</NavigationMenuItem>
</template>
<script setup lang="ts">
import type { NavigationMenuContentEmits } from 'reka-ui'
import type { NNavigationMenuContentProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import {
NavigationMenuContent,
useForwardPropsEmits,
} from 'reka-ui'
import { cn } from '../../utils'
const props = defineProps<NNavigationMenuContentProps>()
const emits = defineEmits<NavigationMenuContentEmits>()
const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<NavigationMenuContent
v-bind="forwarded"
:class="cn(
'navigation-menu-content',
'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-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52',
props.una?.navigationMenuContent,
props.class,
)"
>
<slot />
</NavigationMenuContent>
</template>
<script setup lang="ts">
import type { NNavigationMenuIndicatorProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { NavigationMenuIndicator, useForwardProps } from 'reka-ui'
import { cn } from '../../utils'
const props = defineProps<NNavigationMenuIndicatorProps>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<NavigationMenuIndicator
v-bind="forwardedProps"
:class="cn(
'data-[state=visible]:animate-fadeIn data-[state=hidden]:animate-fadeOut',
'navigation-menu-indicator',
props.una?.navigationMenuIndicator,
props.class,
)"
>
<slot />
<div
class="navigation-menu-indicator-arrow"
:class="props.una?.navigationMenuIndicatorArrow"
/>
</NavigationMenuIndicator>
</template>
<script setup lang="ts">
import type { NNavigationMenuViewportProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { NavigationMenuViewport, useForwardProps } from 'reka-ui'
import { cn } from '../../utils'
defineOptions({
inheritAttrs: false,
})
const props = defineProps<NNavigationMenuViewportProps>()
const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<div
:class="cn(
'navigation-menu-viewport-wrapper',
props.orientation === 'horizontal' && 'navigation-menu-viewport-wrapper-horizontal',
props.orientation === 'vertical' && 'navigation-menu-viewport-wrapper-vertical',
props.una?.navigationMenuViewportWrapper,
props.class,
)"
>
<NavigationMenuViewport
v-bind="{ ...forwardedProps, ...$attrs }"
:class="
cn(
'navigation-menu-viewport',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90',
'data-[state=closed]:fade-out data-[state=open]:fade-in',
props.una?.navigationMenuViewport,
)
"
/>
</div>
</template>
<script setup lang="ts">
import type { NavigationMenuLinkEmits } from 'reka-ui'
import type { NNavigationMenuLinkProps } from '../../types'
import { NavigationMenuLink, useForwardPropsEmits } from 'reka-ui'
import { cn } from '../../utils'
import Button from '../elements/Button.vue'
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps<NNavigationMenuLinkProps>(), {
navigationMenuLink: 'ghost-white',
btn: '~',
as: Button,
})
const emits = defineEmits<NavigationMenuLinkEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<NavigationMenuLink
v-bind="!props.asChild ? {
...$attrs,
...forwarded,
class: cn(
'navigation-menu-link group',
props.una?.navigationMenuLink,
props.class,
),
navigationMenuLink: btn !== '~' ? undefined : navigationMenuLink,
navigationMenu: btn === '~' ? props.navigationMenu : undefined,
} : {}"
:as-child
:as
:active
>
<slot :active="active" />
</NavigationMenuLink>
</template>
<script setup lang="ts">
import type { NNavigationMenuListProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { NavigationMenuList, useForwardProps } from 'reka-ui'
import { cn } from '../../utils'
const props = defineProps<NNavigationMenuListProps>()
const delegatedProps = reactiveOmit(props, ['class'])
const forwardedProps = useForwardProps(delegatedProps)
</script>
<template>
<NavigationMenuList
v-bind="forwardedProps"
:class="
cn(
'group',
'navigation-menu-list',
props.orientation === 'vertical' ? 'navigation-menu-list-vertical' : 'navigation-menu-list-horizontal',
props.una?.navigationMenuList,
props.class,
)
"
>
<slot />
</NavigationMenuList>
</template>
<script setup lang="ts">
import type { NavigationMenuLinkEmits } from 'reka-ui'
import type { NNavigationMenuListItemProps } from '../../types'
import { useForwardPropsEmits } from 'reka-ui'
import { cn } from '../../utils'
import NavigationMenuLink from './NavigationMenuLink.vue'
const props = defineProps<NNavigationMenuListItemProps>()
const emits = defineEmits<NavigationMenuLinkEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<NavigationMenuLink
v-bind="forwarded"
:una
:class="cn(
'navigation-menu-content-item',
props.una?.navigationMenuContentItem,
props.class,
)"
>
<slot>
<div
:class="cn(
'navigation-menu-content-item-wrapper',
props.una?.navigationMenuContentItemWrapper,
)"
>
<p
:class="cn(
'navigation-menu-content-item-label',
props.una?.navigationMenuContentItemLabel,
)"
>
{{ label }}
</p>
<p
v-if="description" :class="cn(
'navigation-menu-content-item-description',
props.una?.navigationMenuContentItemDescription,
)"
>
{{ description }}
</p>
</div>
</slot>
</NavigationMenuLink>
</template>