NavigationMenu 导航菜单

可组合链接和下拉内容面板的导航菜单。

<script setup lang="ts">
  import { NuxtLink } from '#components'
  import {
    NavigationMenu,
    NavigationMenuItem,
    NavigationMenuTrigger,
    NavigationMenuContent,
    NavigationMenuLink,
    Stack,
  } from '@hina-ui/vue'

  const localePath = useLocalePath()
</script>

<template>
  <Stack class="min-h-96 w-full sm:min-h-64" align="start">
    <NavigationMenu label="主导航">
      <NavigationMenuItem value="start">
        <NavigationMenuTrigger>开始</NavigationMenuTrigger>
        <NavigationMenuContent>
          <NavigationMenuLink
            :as="NuxtLink"
            :to="localePath('/guide/installation')"
            description="安装与配置组件库"
          >
            安装
          </NavigationMenuLink>
          <NavigationMenuLink
            :as="NuxtLink"
            :to="localePath('/design/colors')"
            description="颜色角色与主题变量"
          >
            颜色
          </NavigationMenuLink>
          <NavigationMenuLink
            :as="NuxtLink"
            :to="localePath('/design/motion')"
            description="动画时长与缓动曲线"
          >
            动效
          </NavigationMenuLink>
        </NavigationMenuContent>
      </NavigationMenuItem>
      <NavigationMenuItem value="components">
        <NavigationMenuTrigger>组件</NavigationMenuTrigger>
        <NavigationMenuContent class="grid w-[30rem] gap-1 sm:grid-cols-2">
          <NavigationMenuLink
            :as="NuxtLink"
            :to="localePath('/components/button')"
            description="按钮与操作反馈"
          >
            Button
          </NavigationMenuLink>
          <NavigationMenuLink
            :as="NuxtLink"
            :to="localePath('/components/dialog')"
            description="对话框与自定义内容"
          >
            Dialog
          </NavigationMenuLink>
          <NavigationMenuLink
            :as="NuxtLink"
            :to="localePath('/components/form')"
            description="表单状态与异步校验"
          >
            Form
          </NavigationMenuLink>
          <NavigationMenuLink
            :as="NuxtLink"
            :to="localePath('/components/data-table')"
            description="数据展示与列交互"
          >
            DataTable
          </NavigationMenuLink>
        </NavigationMenuContent>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuLink :as="NuxtLink" :to="localePath('/changelog')">
          变更记录
        </NavigationMenuLink>
      </NavigationMenuItem>
    </NavigationMenu>
  </Stack>
</template>
vue

用法

import {
  NavigationMenu,
  NavigationMenuItem,
  NavigationMenuTrigger,
  NavigationMenuContent,
  NavigationMenuLink,
} from '@hina-ui/vue'
ts

NavigationMenu 提供导航地标、列表和共享内容视口。每个顶层条目用 NavigationMenuItem 包裹,内部可以是一个 NavigationMenuLink,也可以是成对的 NavigationMenuTrigger 和 NavigationMenuContent。通过 label 或 aria-labelledby 命名导航。

active 标记当前页面,并生成 aria-current="page"。展开项与当前页面是独立的状态。

<script setup lang="ts">
  import { NavigationMenu, NavigationMenuItem, NavigationMenuLink } from '@hina-ui/vue'
</script>

<template>
  <NavigationMenu label="页面导航">
    <NavigationMenuItem>
      <NavigationMenuLink href="#usage" active>用法</NavigationMenuLink>
    </NavigationMenuItem>
    <NavigationMenuItem>
      <NavigationMenuLink href="#examples">示例</NavigationMenuLink>
    </NavigationMenuItem>
    <NavigationMenuItem>
      <NavigationMenuLink href="#api">API</NavigationMenuLink>
    </NavigationMenuItem>
  </NavigationMenu>
</template>
vue

示例

点击与受控状态

默认支持鼠标悬停及点击展开。trigger="click" 关闭悬停触发,移开鼠标也不会关闭面板。v-model 绑定展开项的 value,空字符串表示全部关闭;受控使用时为各面板条目指定稳定且唯一的 value。

选择链接后默认关闭面板,@select.prevent 可以阻止关闭,链接本身的跳转不受影响。阻止跳转请使用 @click.prevent。

展开项:无

<script setup lang="ts">
  import { ref } from 'vue'
  import {
    NavigationMenu,
    NavigationMenuItem,
    NavigationMenuTrigger,
    NavigationMenuContent,
    NavigationMenuLink,
    Stack,
    Text,
  } from '@hina-ui/vue'

  const value = ref('')
</script>

<template>
  <Stack class="min-h-56 w-full" align="start">
    <Text size="sm" tone="muted">展开项:{{ value || '无' }}</Text>
    <NavigationMenu v-model="value" label="点击展开" trigger="click">
      <NavigationMenuItem value="links">
        <NavigationMenuTrigger>链接</NavigationMenuTrigger>
        <NavigationMenuContent>
          <NavigationMenuLink href="#controlled">选择后关闭</NavigationMenuLink>
          <NavigationMenuLink href="#controlled" @select.prevent>选择后保持打开</NavigationMenuLink>
        </NavigationMenuContent>
      </NavigationMenuItem>
    </NavigationMenu>
  </Stack>
</template>
vue

自定义内容

NavigationMenuContent 的默认插槽可以放置任意内容,通过 class 控制宽度与布局。默认有内边距,:padded="false" 可关闭。

NavigationMenuLink 提供 icon、description 和 trailing 插槽。下面组合了 Text、Divider 和 Tag。面板中的导航链接也应使用 NavigationMenuLink,以保留键盘导航与选择后关闭的行为。

<script setup lang="ts">
  import { NuxtLink } from '#components'
  import { BookOpen, ArrowUpRight, Palette } from '@lucide/vue'
  import {
    NavigationMenu,
    NavigationMenuItem,
    NavigationMenuTrigger,
    NavigationMenuContent,
    NavigationMenuLink,
    Stack,
    Text,
    Tag,
    Divider,
  } from '@hina-ui/vue'

  const localePath = useLocalePath()
</script>

<template>
  <Stack class="min-h-72 w-full" align="start">
    <NavigationMenu label="自定义导航">
      <NavigationMenuItem value="resources">
        <NavigationMenuTrigger>
          <template #icon><BookOpen /></template>
          资源
        </NavigationMenuTrigger>
        <NavigationMenuContent :padded="false" class="w-96">
          <Stack gap="xs" class="p-4">
            <Text weight="medium">设计与组件</Text>
            <Text size="sm" tone="muted">内容区域由插槽自由组合。</Text>
          </Stack>
          <Divider />
          <Stack gap="xs" class="p-2">
            <NavigationMenuLink
              :as="NuxtLink"
              :to="localePath('/design/colors')"
              description="语义色、表现色与主题切换"
            >
              <template #icon><Palette /></template>
              颜色规范
              <template #trailing><Tag size="sm">更新</Tag></template>
            </NavigationMenuLink>
            <NavigationMenuLink
              href="https://github.com/Hikarinagi/ui"
              target="_blank"
              rel="noopener noreferrer"
            >
              GitHub
              <template #trailing><ArrowUpRight /></template>
            </NavigationMenuLink>
          </Stack>
        </NavigationMenuContent>
      </NavigationMenuItem>
    </NavigationMenu>
  </Stack>
</template>
vue

纵向

orientation="vertical" 纵向排列导航项,内容面板默认从行末侧展开。空间不足时会使用另一侧并限制内容宽度。align 控制面板与触发器的对齐。

<script setup lang="ts">
  import { NuxtLink } from '#components'
  import {
    NavigationMenu,
    NavigationMenuItem,
    NavigationMenuTrigger,
    NavigationMenuContent,
    NavigationMenuLink,
    Stack,
  } from '@hina-ui/vue'

  const localePath = useLocalePath()
</script>

<template>
  <Stack class="min-h-56 w-full" align="start">
    <NavigationMenu label="纵向导航" orientation="vertical" align="start" size="sm">
      <NavigationMenuItem value="design">
        <NavigationMenuTrigger>设计规范</NavigationMenuTrigger>
        <NavigationMenuContent class="w-64">
          <NavigationMenuLink :as="NuxtLink" :to="localePath('/design/colors')">
            颜色
          </NavigationMenuLink>
          <NavigationMenuLink :as="NuxtLink" :to="localePath('/design/typography')">
            排版
          </NavigationMenuLink>
          <NavigationMenuLink :as="NuxtLink" :to="localePath('/design/layout')">
            布局
          </NavigationMenuLink>
        </NavigationMenuContent>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuLink :as="NuxtLink" :to="localePath('/components')">组件</NavigationMenuLink>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuLink :as="NuxtLink" :to="localePath('/changelog')">
          变更记录
        </NavigationMenuLink>
      </NavigationMenuItem>
    </NavigationMenu>
  </Stack>
</template>
vue

尺寸

size 统一控制链接、触发器和图标的尺寸,支持 sm、md、lg,并跟随全局密度。图标尺寸分别为 14、16、18px。

<script setup lang="ts">
  import { BookOpen, Code, ArrowUpRight } from '@lucide/vue'
  import {
    NavigationMenu,
    NavigationMenuItem,
    NavigationMenuTrigger,
    NavigationMenuContent,
    NavigationMenuLink,
    Stack,
  } from '@hina-ui/vue'
</script>

<template>
  <Stack gap="lg" align="start" class="min-h-80 w-full">
    <NavigationMenu
      v-for="size in ['sm', 'md', 'lg'] as const"
      :key="size"
      :label="`${size} 导航`"
      :size="size"
    >
      <NavigationMenuItem value="docs">
        <NavigationMenuTrigger>
          <template #icon><BookOpen /></template>
          {{ size }}
        </NavigationMenuTrigger>
        <NavigationMenuContent class="w-64">
          <NavigationMenuLink href="#usage">
            <template #icon><BookOpen /></template>
            用法
          </NavigationMenuLink>
          <NavigationMenuLink href="#api">
            <template #icon><Code /></template>
            API
            <template #trailing><ArrowUpRight /></template>
          </NavigationMenuLink>
        </NavigationMenuContent>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuLink href="#usage" active>用法</NavigationMenuLink>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuLink href="#api">API</NavigationMenuLink>
      </NavigationMenuItem>
    </NavigationMenu>
  </Stack>
</template>
vue

状态

触发器和链接都支持 disabled。禁用项无法激活,方向键导航会跳过它们。

<script setup lang="ts">
  import {
    NavigationMenu,
    NavigationMenuItem,
    NavigationMenuTrigger,
    NavigationMenuLink,
  } from '@hina-ui/vue'
</script>

<template>
  <NavigationMenu label="导航状态" size="sm">
    <NavigationMenuItem>
      <NavigationMenuLink href="#states" active>当前页</NavigationMenuLink>
    </NavigationMenuItem>
    <NavigationMenuItem>
      <NavigationMenuLink href="#api">普通链接</NavigationMenuLink>
    </NavigationMenuItem>
    <NavigationMenuItem>
      <NavigationMenuTrigger disabled>禁用面板</NavigationMenuTrigger>
    </NavigationMenuItem>
    <NavigationMenuItem>
      <NavigationMenuLink href="#states" disabled>禁用链接</NavigationMenuLink>
    </NavigationMenuItem>
  </NavigationMenu>
</template>
vue

路由链接

NavigationMenuLink 的 as-child 将属性和交互传给唯一的子元素,可承接 RouterLink 或 NuxtLink;也可以通过 as 指定组件。active 由调用方根据路由状态设置。

<script setup lang="ts">
  import { NavigationMenu, NavigationMenuItem, NavigationMenuLink } from '@hina-ui/vue'

  const localePath = useLocalePath()
</script>

<template>
  <NavigationMenu label="路由导航">
    <NavigationMenuItem>
      <NavigationMenuLink as-child active>
        <NuxtLink :to="localePath('/components/navigation-menu')">NavigationMenu</NuxtLink>
      </NavigationMenuLink>
    </NavigationMenuItem>
    <NavigationMenuItem>
      <NavigationMenuLink as-child>
        <NuxtLink :to="localePath('/components/toolbar')">Toolbar</NuxtLink>
      </NavigationMenuLink>
    </NavigationMenuItem>
  </NavigationMenu>
</template>
vue

RTL

方向默认继承外层 dir 或 Reka 的全局配置,也可显式设置 dir="rtl"。排列、对齐、箭头和键盘方向一起切换。

<script setup lang="ts">
  import {
    NavigationMenu,
    NavigationMenuItem,
    NavigationMenuTrigger,
    NavigationMenuContent,
    NavigationMenuLink,
    Stack,
  } from '@hina-ui/vue'
</script>

<template>
  <Stack class="min-h-56 w-full" dir="rtl" align="start">
    <NavigationMenu label="RTL 导航" align="start">
      <NavigationMenuItem value="first">
        <NavigationMenuTrigger>第一项</NavigationMenuTrigger>
        <NavigationMenuContent class="w-64">
          <NavigationMenuLink href="#rtl">链接一</NavigationMenuLink>
          <NavigationMenuLink href="#rtl">链接二</NavigationMenuLink>
        </NavigationMenuContent>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuLink href="#usage">第二项</NavigationMenuLink>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuLink href="#api">第三项</NavigationMenuLink>
      </NavigationMenuItem>
    </NavigationMenu>
  </Stack>
</template>
vue

行为

  • 同一导航中同时展开一个面板,切换时共享视口平滑调整宽高,面板按切换方向过渡。
  • 内容在导航元素内渲染,浮在正常文档流上方。父级需允许溢出显示,避免用 overflow: hidden 裁掉面板。
  • 面板宽度与横向定位会考虑浏览器及滚动、裁切容器的边界;纵向仍需预留展开空间。内容特别长时,可在面板内组合 ScrollArea。
  • 离开导航和内容后延迟关闭;关闭动画期间面板停止接收指针事件。
  • 点击外部、焦点移出导航或按 Esc 关闭面板。:unmount-on-hide="false" 保留隐藏内容中的组件状态。
  • 导航使用 nav、ul、li 和链接语义。需要菜单命令时使用 Menubar 或 DropdownMenu。

无障碍

按键行为
Tab / Shift+Tab按顺序访问导航及展开的内容
Enter / Space展开或收起触发器
横向 ← / →,纵向 ↑ / ↓在顶层条目之间移动焦点,跳过禁用项;RTL 下横向方向反转
Home / End聚焦第一个或最后一个可用的顶层条目
横向 ↓,纵向 →(RTL 为 ←)从已展开的触发器进入内容
Esc关闭内容并将焦点还给触发器

API

NavigationMenu

属性
类型
默认值
说明
modelValue
string
''
展开项的值,空字符串为关闭
label
string
—
导航的可访问名称
orientation
'horizontal' | 'vertical'
'horizontal'
排列方向
dir
'ltr' | 'rtl'
继承
阅读方向
size
'sm' | 'md' | 'lg'
'md'
控件尺寸
trigger
'hover' | 'click'
'hover'
悬停及点击,或仅点击
delayDuration
number
200
首次悬停展开的等待时间,毫秒
skipDelayDuration
number
300
关闭后再次进入时跳过首次等待的时间窗口,毫秒
align
'start' | 'center' | 'end'
'center'
面板与触发器对齐
unmountOnHide
boolean
true
隐藏后卸载内容
class
string
—
导航根节点类名
listClass
string
—
列表类名
viewportClass
string
—
共享内容视口类名

事件 update:modelValue(value: string) 返回展开项。默认插槽参数为 { value: string }。

NavigationMenuItem

value?: string 关联展开状态;省略时自动生成。默认插槽放链接或触发器与内容面板。

NavigationMenuTrigger

属性
类型
默认值
说明
disabled
boolean
false
禁用触发器
class
string
—
触发器类名

默认插槽是名称,icon 放前缀图标,trailing 可替换默认的 DisclosureIcon。箭头随展开状态旋转,纵向排列时指向行末侧。渲染为 type="button",不会触发表单提交。

NavigationMenuContent

属性
类型
默认值
说明
padded
boolean
true
是否保留内边距
class
string
—
内容类名,可控制宽度和布局

默认插槽是内容。透传 escapeKeyDown、pointerDownOutside、focusOutside、interactOutside、dismiss 事件;可在相应事件上调用 preventDefault() 阻止默认关闭。