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 hover:bg-accent p-2.5 transition-colors"
: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
NavigationMenu
structure, 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 focus:bg-accent hover:bg-accent p-3 focus:text-accent hover:text-accent leading-none no-underline outline-none transition-colors space-y-1"
>
<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 focus:bg-accent hover:bg-accent p-3 focus:text-accent hover:text-accent leading-none no-underline outline-none transition-colors space-y-1"
>
<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 focus:bg-accent hover:bg-accent p-3 focus:text-accent hover:text-accent leading-none no-underline outline-none transition-colors space-y-1"
>
<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 focus:bg-accent hover:bg-accent p-3 focus:text-accent hover:text-accent leading-none no-underline outline-none transition-colors space-y-1"
>
<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 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>
<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(
'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',
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 { NavigationMenuList, useForwardProps } from 'reka-ui'
import { computed } from 'vue'
import { cn } from '../../utils'
const props = defineProps<NNavigationMenuListProps>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
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"
: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>