Reka UI logoReka
backdrop
组件

导航菜单

用于网站导航的链接集合。

功能

  • 可受控或非受控。
  • 灵活的布局结构,支持管理选项卡焦点。
  • 支持子菜单。
  • 可选的活动项指示器。
  • 完整的键盘导航。
  • 暴露 CSS 变量以实现高级动画。
  • 支持自定义时间。

安装

从命令行安装此组件。

sh
$ npm add reka-ui

结构

导入所有部分并将其组合。

vue
<script setup lang="ts">
import {
  NavigationMenuContent,
  NavigationMenuIndicator,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuRoot,
  NavigationMenuSub,
  NavigationMenuTrigger,
  NavigationMenuViewport,
} from 'reka-ui'
</script>

<template>
  <NavigationMenuRoot>
    <NavigationMenuList>
      <NavigationMenuItem>
        <NavigationMenuTrigger />
        <NavigationMenuContent>
          <NavigationMenuLink />
        </NavigationMenuContent>
      </NavigationMenuItem>

      <NavigationMenuItem>
        <NavigationMenuLink />
      </NavigationMenuItem>

      <NavigationMenuItem>
        <NavigationMenuTrigger />
        <NavigationMenuContent>
          <NavigationMenuSub>
            <NavigationMenuList />
            <NavigationMenuViewport />
          </NavigationMenuSub>
        </NavigationMenuContent>
      </NavigationMenuItem>

      <NavigationMenuIndicator />
    </NavigationMenuList>

    <NavigationMenuViewport />
  </NavigationMenuRoot>
</template>

API 参考

包含导航菜单的所有部分。

属性默认类型
as
'nav'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

defaultValue
string

菜单项的初始活动值。

当您不需要控制值状态时使用。

delayDuration
200
数字

从指针进入触发器到工具提示打开的持续时间。

dir
'ltr' | 'rtl'

适用时,组合框的阅读方向。

如果省略,则全局继承自 ConfigProvider 或假定为 LTR(从左到右)阅读模式。

disableClickTrigger
false
布尔值

如果true,菜单不能通过点击触发器打开。

disableHoverTrigger
false
布尔值

如果true,菜单不能通过悬停在触发器上打开。

disablePointerLeaveClose
布尔值

如果true,菜单在指针离开事件期间不会关闭。

modelValue
string

要激活的菜单项的受控值。可用作v-model

orientation
'horizontal'
'vertical' | 'horizontal'

菜单的方向。

skipDelayDuration
300
数字

用户在不再次产生延迟的情况下进入另一个触发器的时间。

unmountOnHide
true
布尔值

true 时,元素在关闭状态时将被卸载。

事件触发载荷
update:modelValue
[value: string]

值改变时调用的事件处理程序。

插槽(默认)载荷
modelValue
string

当前输入值

数据属性
[data-orientation]"垂直" | "水平"

子菜单

表示一个子菜单。在嵌套时,用它替代根组件以创建子菜单。

属性默认类型
as
'div'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

defaultValue
string

菜单项的初始活动值。

当您不需要控制值状态时使用。

modelValue
string

要激活的子菜单项的受控值。可用作v-model

orientation
'horizontal'
'vertical' | 'horizontal'

菜单的方向。

事件触发载荷
update:modelValue
[value: string]

值改变时调用的事件处理程序。

插槽(默认)载荷
modelValue
string

当前输入值

数据属性
[data-orientation]"垂直" | "水平"

列表

包含顶级菜单项。

属性默认类型
as
'ul'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

数据属性
[data-orientation]"垂直" | "水平"

菜单项

一个顶级菜单项,包含链接或触发器与内容的组合。

属性默认类型
as
'li'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

value
string

当导航菜单受控时,将项与活动值关联的唯一值。

当不受控时,此属性会自动管理。

触发器

切换内容的按钮。

属性默认类型
as
'button'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

disabled
布尔值

当为 true 时,阻止用户与项目交互

数据属性
[data-state]"打开" | "关闭"
[data-disabled]禁用时存在

内容

包含与每个触发器关联的内容。

提示
使用 Presence 组件构建 - 支持任何 动画技术,同时保持对 Presence 发出事件的访问。
属性默认类型
as
'div'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

disableOutsidePointerEvents
布尔值

当为true时,在DismissableLayer外部的元素的悬停/焦点/点击交互将被禁用。用户需要点击外部元素两次才能与之交互:第一次关闭DismissableLayer,第二次触发该元素。

forceMount
布尔值

当需要更多控制时,用于强制挂载。在与 Vue 动画库控制动画时很有用。

事件触发载荷
escapeKeyDown
[事件: KeyboardEvent]

按下 Esc 键时调用的事件处理程序。可阻止其默认行为。

focusOutside
[事件: FocusOutsideEvent]

当焦点移出 DismissableLayer 时调用的事件处理程序。可阻止其默认行为。

interactOutside
[事件: PointerDownOutsideEvent | FocusOutsideEvent]

DismissableLayer 外部发生交互时调用的事件处理程序。具体来说,当 pointerdown 事件在外部发生或焦点移出时。可阻止其默认行为。

pointerDownOutside
[事件: PointerDownOutsideEvent]

pointerdown 事件在 DismissableLayer 外部发生时调用的事件处理程序。可阻止其默认行为。

数据属性
[data-state]"打开" | "关闭"
[data-motion]"to-start" | "to-end" | "from-start" | "from-end"
[data-orientation]"垂直" | "水平"

一个导航链接。

属性默认类型
active
布尔值

用于标识该链接为当前活动页面。

as
'a'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

事件触发载荷
select
[payload: CustomEvent<{ originalEvent: Event; }>]

当用户选择链接(通过鼠标或键盘)时调用的事件处理程序。

在此处理程序中调用event.preventDefault将阻止在选择该链接时导航菜单关闭。

数据属性
[data-active]激活时存在

指示器

一个可选的指示器元素,渲染在列表下方,用于突出显示当前活动的触发器。

提示
使用 Presence 组件构建 - 支持任何 动画技术,同时保持对 Presence 发出事件的访问。
属性默认类型
as
'div'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

forceMount
布尔值

当需要更多控制时,用于强制挂载。在与 Vue 动画库控制动画时很有用。

数据属性
[data-state]"可见" | "隐藏"
[data-orientation]"垂直" | "水平"
CSS 变量描述
--reka-navigation-menu-indicator-size
指示器的大小。
--reka-navigation-menu-indicator-position
指示器的位置

视口

一个可选的视口元素,用于在列表外部渲染活动内容。

提示
使用 Presence 组件构建 - 支持任何 动画技术,同时保持对 Presence 发出事件的访问。
属性默认类型
align
'center'
'开始' | '居中' | '结束'

视口相对于 CSS 变量(--reka-navigation-menu-viewport-left, --reka-navigation-menu-viewport-top)的放置位置。

as
'div'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

forceMount
布尔值

当需要更多控制时,用于强制挂载。在与 Vue 动画库控制动画时很有用。

数据属性
[data-state]"可见" | "隐藏"
[data-orientation]"垂直" | "水平"
CSS 变量描述
--reka-navigation-menu-viewport-width
视口可见/隐藏时的宽度,根据活动内容计算。
--reka-navigation-menu-viewport-height
视口可见/隐藏时的高度,根据活动内容计算。

示例

垂直方向

您可以使用orientation属性创建垂直菜单。

vue
<script setup lang="ts">
import {
  NavigationMenuContent,
  NavigationMenuIndicator,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuRoot,
  NavigationMenuSub,
  NavigationMenuTrigger,
  NavigationMenuViewport,
} from 'reka-ui'
</script>

<template>
  <NavigationMenuRoot orientation="vertical">
    <NavigationMenuList>
      <NavigationMenuItem>
        <NavigationMenuTrigger>Item one</NavigationMenuTrigger>
        <NavigationMenuContent>Item one content</NavigationMenuContent>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuTrigger>Item two</NavigationMenuTrigger>
        <NavigationMenuContent>Item Two content</NavigationMenuContent>
      </NavigationMenuItem>
    </NavigationMenuList>
  </NavigationMenuRoot>
</template>

灵活布局

当您需要额外控制Content的渲染位置时,请使用Viewport部分。这在您的设计需要调整 DOM 结构或您需要灵活性以实现高级动画时会很有帮助。选项卡焦点将自动维护。

vue
<script setup lang="ts">
import {
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuList,
  NavigationMenuRoot,
  NavigationMenuTrigger,
  NavigationMenuViewport,
} from 'reka-ui'
</script>

<template>
  <NavigationMenuRoot>
    <NavigationMenuList>
      <NavigationMenuItem>
        <NavigationMenuTrigger>Item one</NavigationMenuTrigger>
        <NavigationMenuContent>Item one content</NavigationMenuContent>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuTrigger>Item two</NavigationMenuTrigger>
        <NavigationMenuContent>Item two content</NavigationMenuContent>
      </NavigationMenuItem>
    </NavigationMenuList>

    <!-- NavigationMenuContent will be rendered here when active  -->
    <NavigationMenuViewport />
  </NavigationMenuRoot>
</template>

带指示器

您可以使用可选的Indicator部分来突出显示当前活动的Trigger,这在您希望提供动画视觉提示(例如箭头或高亮)以配合Viewport时非常有用。

vue
<script setup lang="ts">
import {
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuList,
  NavigationMenuRoot,
  NavigationMenuTrigger,
  NavigationMenuViewport,
} from 'reka-ui'
</script>

<template>
  <NavigationMenuRoot>
    <NavigationMenuList>
      <NavigationMenuItem>
        <NavigationMenuTrigger>Item one</NavigationMenuTrigger>
        <NavigationMenuContent>Item one content</NavigationMenuContent>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuTrigger>Item two</NavigationMenuTrigger>
        <NavigationMenuContent>Item two content</NavigationMenuContent>
      </NavigationMenuItem>

      <NavigationMenuIndicator class="NavigationMenuIndicator" />
    </NavigationMenuList>

    <NavigationMenuViewport />
  </NavigationMenuRoot>
</template>
css
/* styles.css */
.NavigationMenuIndicator {
  background-color: grey;
  position: absolute;
  transition: width, transform, 250ms ease;
}

.NavigationMenuIndicator[data-orientation="horizontal"] {
  left: 0;
  height: 3px;
  transform: translateX(var(--reka-navigation-menu-indicator-position));
  width: var(--reka-navigation-menu-indicator-size);
}

带子菜单

通过嵌套您的NavigationMenu并使用Sub部分替代其Root来创建子菜单。子菜单与Root导航菜单的工作方式不同,它们类似于Tabs,即始终应有一个项目处于活动状态,因此请务必分配并设置defaultValue

vue
<script setup lang="ts">
import {
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuList,
  NavigationMenuRoot,
  NavigationMenuSub,
  NavigationMenuTrigger,
  NavigationMenuViewport,
} from 'reka-ui'
</script>

<template>
  <NavigationMenuRoot>
    <NavigationMenuList>
      <NavigationMenuItem>
        <NavigationMenuTrigger>Item one</NavigationMenuTrigger>
        <NavigationMenuContent>Item one content</NavigationMenuContent>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuTrigger>Item two</NavigationMenuTrigger>
        <NavigationMenuContent>
          <NavigationMenuSub default-value="sub1">
            <NavigationMenuList>
              <NavigationMenuItem value="sub1">
                <NavigationMenuTrigger>Sub item one</NavigationMenuTrigger>
                <NavigationMenuContent> Sub item one content </NavigationMenuContent>
              </NavigationMenuItem>
              <NavigationMenuItem value="sub2">
                <NavigationMenuTrigger>Sub item two</NavigationMenuTrigger>
                <NavigationMenuContent> Sub item two content </NavigationMenuContent>
              </NavigationMenuItem>
            </NavigationMenuList>
          </NavigationMenuSub>
        </NavigationMenuContent>
      </NavigationMenuItem>
    </NavigationMenuList>
  </NavigationMenuRoot>
</template>

与客户端路由结合

如果您需要使用路由包提供的RouterLink组件,我们建议在NavigationMenuLink中添加asChild="true",或设置as="RouterLink"。这将确保可访问性和键盘控制的一致性。

vue
<script setup lang="ts">
import { NavigationMenuItem, NavigationMenuList, NavigationMenuRoot } from 'reka-ui'

// RouterLink should be injected by default if using `vue-router`
</script>

<template>
  <NavigationMenuRoot>
    <NavigationMenuList>
      <NavigationMenuItem>
        <NavigationMenuLink as-child>
          <RouterLink to="/">
            Home
          </RouterLink>
          <NavigationMenuLink />
        </NavigationMenuLink>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuLink
          :as="RouterLink"
          to="/about"
        >
          About
        </NavigationMenuLink>
      </NavigationMenuItem>
    </NavigationMenuList>
  </NavigationMenuRoot>
</template>

高级动画

我们暴露了--reka-navigation-menu-viewport-[width|height]data-motion['from-start'|'to-start'|'from-end'|'to-end']属性,以便您可以根据进入/退出方向来动画Viewport大小和Content位置。

将这些与position: absolute;结合使用,可以在项目之间移动时创建平滑的重叠动画效果。

vue
<script setup lang="ts">
import {
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuList,
  NavigationMenuRoot,
  NavigationMenuTrigger,
  NavigationMenuViewport,
} from 'reka-ui'
</script>

<template>
  <NavigationMenuRoot>
    <NavigationMenuList>
      <NavigationMenuItem>
        <NavigationMenuTrigger>Item one</NavigationMenuTrigger>
        <NavigationMenuContent class="NavigationMenuContent">
          Item one content
        </NavigationMenuContent>
      </NavigationMenuItem>
      <NavigationMenuItem>
        <NavigationMenuTrigger>Item two</NavigationMenuTrigger>
        <NavigationMenuContent class="NavigationMenuContent">
          Item two content
        </NavigationMenuContent>
      </NavigationMenuItem>
    </NavigationMenuList>

    <NavigationMenuViewport class="NavigationMenuViewport" />
  </NavigationMenuRoot>
</template>
css
/* styles.css */
.NavigationMenuContent {
  position: absolute;
  top: 0;
  left: 0;
  animation-duration: 250ms;
  animation-timing-function: ease;
}
.NavigationMenuContent[data-motion="from-start"] {
  animation-name: enterFromLeft;
}
.NavigationMenuContent[data-motion="from-end"] {
  animation-name: enterFromRight;
}
.NavigationMenuContent[data-motion="to-start"] {
  animation-name: exitToLeft;
}
.NavigationMenuContent[data-motion="to-end"] {
  animation-name: exitToRight;
}

.NavigationMenuViewport {
  position: relative;
  width: var(--reka-navigation-menu-viewport-width);
  height: var(--reka-navigation-menu-viewport-height);
  transition: width, height, 250ms ease;
}

@keyframes enterFromRight {
  from {
    opacity: 0;
    transform: translateX(200px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes enterFromLeft {
  from {
    opacity: 0;
    transform: translateX(-200px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes exitToRight {
  from {
    opacity: 1;
    transform: translateX(0);
  }
  to {
    opacity: 0;
    transform: translateX(200px);
  }
}

@keyframes exitToLeft {
  from {
    opacity: 1;
    transform: translateX(0);
  }
  to {
    opacity: 0;
    transform: translateX(-200px);
  }
}

可访问性

遵守navigation角色要求

与菜单栏的区别

NavigationMenu不应与menubar混淆,尽管这个基本元素在口语上共享menu的名称以指代一组导航链接,但它不使用 WAI-ARIA menu角色。这是因为menumenubars的行为类似于桌面应用程序窗口中最常见的原生操作系统菜单,因此它们具有复合焦点管理和首字符导航等复杂功能。

这些功能通常被认为对于网站导航来说是不必要的,最坏的情况是可能会混淆熟悉现有网站模式的用户。

有关更多信息,请参阅 W3C 披露导航菜单示例。

在菜单中,所有导航链接都必须使用NavigationMenuLink,这不仅适用于主列表,也适用于通过NavigationMenuContent渲染的任何内容。这将确保键盘交互和可访问性的一致性,同时还提供对active属性的访问,用于设置aria-current和活动样式。有关与第三方路由组件一起使用的更多信息,请参阅此示例

键盘交互

描述
SpaceEnter
当焦点位于NavigationMenuTrigger上时,打开内容。
Tab
将焦点移动到下一个可聚焦元素。
向下箭头键
当为horizontal且焦点位于打开的NavigationMenuTrigger上时,将焦点移入NavigationMenuContent
将焦点移动到下一个NavigationMenuTriggerNavigationMenuLink
向上箭头键
将焦点移动到上一个NavigationMenuTriggerNavigationMenuLink
向右箭头键向左箭头键
当为vertical且焦点位于打开的NavigationMenuTrigger上时,将焦点移入其NavigationMenuContent
将焦点移动到下一个/上一个NavigationMenuTriggerNavigationMenuLink
HomeEnd
将焦点移动到第一个/最后一个NavigationMenu.TriggerNavigationMenu.Link
Esc键
关闭打开的NavigationMenu.Content并将焦点移至其NavigationMenu.Trigger