NavigationMenu 导航菜单

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

'use client'

import NextLink from 'next/link'
import {
  NavigationMenu,
  NavigationMenuItem,
  NavigationMenuTrigger,
  NavigationMenuContent,
  NavigationMenuLink,
  Stack,
} from '@hina-ui/react'

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

用法

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

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

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

import { NavigationMenu, NavigationMenuItem, NavigationMenuLink } from '@hina-ui/react'

export default function Demo() {
  return (
    <NavigationMenu label="页面导航">
      <NavigationMenuItem>
        <NavigationMenuLink href="#usage" active>
          用法
        </NavigationMenuLink>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuLink href="#examples">示例</NavigationMenuLink>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuLink href="#api">API</NavigationMenuLink>
      </NavigationMenuItem>
    </NavigationMenu>
  )
}
tsx

示例

点击与受控状态

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

选择链接后默认关闭面板,在 onSelect 中调用 event.preventDefault() 可以阻止关闭,链接本身的跳转不受影响。阻止跳转请在 onClick 中调用 event.preventDefault()。

展开项:无

'use client'

import { useState } from 'react'
import {
  NavigationMenu,
  NavigationMenuItem,
  NavigationMenuTrigger,
  NavigationMenuContent,
  NavigationMenuLink,
  Stack,
  Text,
} from '@hina-ui/react'

export default function Demo() {
  const [value, setValue] = useState('')
  return (
    <Stack className="min-h-56 w-full" align="start">
      <Text size="sm" tone="muted">
        展开项:{value || '无'}
      </Text>
      <NavigationMenu value={value} onValueChange={setValue} label="点击展开" trigger="click">
        <NavigationMenuItem value="links">
          <NavigationMenuTrigger>链接</NavigationMenuTrigger>
          <NavigationMenuContent>
            <NavigationMenuLink href="#controlled">选择后关闭</NavigationMenuLink>
            <NavigationMenuLink href="#controlled" onSelect={event => event.preventDefault()}>
              选择后保持打开
            </NavigationMenuLink>
          </NavigationMenuContent>
        </NavigationMenuItem>
      </NavigationMenu>
    </Stack>
  )
}
tsx

自定义内容

NavigationMenuContent 的 children 可以放置任意内容,通过 className 控制宽度与布局。默认有内边距,padded={false} 可关闭。

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

'use client'

import NextLink from 'next/link'
import { BookOpen, ArrowUpRight, Palette } from 'lucide-react'
import {
  NavigationMenu,
  NavigationMenuItem,
  NavigationMenuTrigger,
  NavigationMenuContent,
  NavigationMenuLink,
  Stack,
  Text,
  Tag,
  Divider,
} from '@hina-ui/react'

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

纵向

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

'use client'

import NextLink from 'next/link'
import {
  NavigationMenu,
  NavigationMenuItem,
  NavigationMenuTrigger,
  NavigationMenuContent,
  NavigationMenuLink,
  Stack,
} from '@hina-ui/react'

export default function Demo() {
  return (
    <Stack className="min-h-56 w-full" align="start">
      <NavigationMenu label="纵向导航" orientation="vertical" align="start" size="sm">
        <NavigationMenuItem value="design">
          <NavigationMenuTrigger>设计规范</NavigationMenuTrigger>
          <NavigationMenuContent className="w-64">
            <NavigationMenuLink as={NextLink} href="/design/colors">
              颜色
            </NavigationMenuLink>
            <NavigationMenuLink as={NextLink} href="/design/typography">
              排版
            </NavigationMenuLink>
            <NavigationMenuLink as={NextLink} href="/design/layout">
              布局
            </NavigationMenuLink>
          </NavigationMenuContent>
        </NavigationMenuItem>
        <NavigationMenuItem>
          <NavigationMenuLink as={NextLink} href="/components">
            组件
          </NavigationMenuLink>
        </NavigationMenuItem>
        <NavigationMenuItem>
          <NavigationMenuLink as={NextLink} href="/changelog">
            变更记录
          </NavigationMenuLink>
        </NavigationMenuItem>
      </NavigationMenu>
    </Stack>
  )
}
tsx

尺寸

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

import { BookOpen, Code, ArrowUpRight } from 'lucide-react'
import {
  NavigationMenu,
  NavigationMenuItem,
  NavigationMenuTrigger,
  NavigationMenuContent,
  NavigationMenuLink,
  Stack,
} from '@hina-ui/react'

export default function Demo() {
  return (
    <Stack gap="lg" align="start" className="min-h-80 w-full">
      {(['sm', 'md', 'lg'] as const).map(size => (
        <NavigationMenu key={size} label={`${size} 导航`} size={size}>
          <NavigationMenuItem value="docs">
            <NavigationMenuTrigger icon={<BookOpen />}>{size}</NavigationMenuTrigger>
            <NavigationMenuContent className="w-64">
              <NavigationMenuLink href="#usage" icon={<BookOpen />}>
                用法
              </NavigationMenuLink>
              <NavigationMenuLink href="#api" icon={<Code />} trailing={<ArrowUpRight />}>
                API
              </NavigationMenuLink>
            </NavigationMenuContent>
          </NavigationMenuItem>
          <NavigationMenuItem>
            <NavigationMenuLink href="#usage" active>
              用法
            </NavigationMenuLink>
          </NavigationMenuItem>
          <NavigationMenuItem>
            <NavigationMenuLink href="#api">API</NavigationMenuLink>
          </NavigationMenuItem>
        </NavigationMenu>
      ))}
    </Stack>
  )
}
tsx

状态

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

import {
  NavigationMenu,
  NavigationMenuItem,
  NavigationMenuTrigger,
  NavigationMenuLink,
} from '@hina-ui/react'

export default function Demo() {
  return (
    <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>
  )
}
tsx

路由链接

NavigationMenuLink 的 asChild 将属性和交互传给唯一的子元素,可承接 next/link,在 Server Component 中也可使用;也可以在客户端组件中通过 as 指定组件。active 由调用方根据路由状态设置。

import NextLink from 'next/link'
import { NavigationMenu, NavigationMenuItem, NavigationMenuLink } from '@hina-ui/react'

export default function Demo() {
  return (
    <NavigationMenu label="路由导航">
      <NavigationMenuItem>
        <NavigationMenuLink asChild active>
          <NextLink href="/components/navigation-menu">NavigationMenu</NextLink>
        </NavigationMenuLink>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuLink asChild>
          <NextLink href="/components/toolbar">Toolbar</NextLink>
        </NavigationMenuLink>
      </NavigationMenuItem>
    </NavigationMenu>
  )
}
tsx

RTL

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

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

export default function Demo() {
  return (
    <Stack className="min-h-56 w-full" dir="rtl" align="start">
      <NavigationMenu label="RTL 导航" align="start">
        <NavigationMenuItem value="first">
          <NavigationMenuTrigger>第一项</NavigationMenuTrigger>
          <NavigationMenuContent className="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>
  )
}
tsx

行为

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

无障碍

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

API

NavigationMenu

属性
类型
默认值
说明
value
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
隐藏后卸载内容
className
string
—
导航根节点类名
listClass
string
—
列表类名
viewportClass
string
—
共享内容视口类名

回调 onValueChange(value: string) 返回展开项。children 也可以是函数,参数为 { value: string }。

NavigationMenuItem

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

NavigationMenuTrigger

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

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

NavigationMenuContent

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

children 是内容。透传 onEscapeKeyDown、onPointerDownOutside、onFocusOutside、onInteractOutside 回调;可在回调中调用 event.preventDefault() 阻止默认关闭。