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 参考

Root

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

PropDefaultType
as
'nav'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

defaultValue
string

The value of the menu item that should be active when initially rendered.

Use when you do not need to control the value state.

delayDuration
200
number

The duration from when the pointer enters the trigger until the tooltip gets opened.

dir
'ltr' | 'rtl'

The reading direction of the combobox when applicable.

If omitted, inherits globally from ConfigProvider or assumes LTR (left-to-right) reading mode.

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.

orientation
'horizontal'
'vertical' | 'horizontal'

The orientation of the menu.

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.

EmitPayload
update:modelValue
[value: string]

Event handler called when the value changes.

Slots (default)Payload
modelValue
string

Current input values

Data AttributeValue
[data-orientation]"vertical" | "horizontal"

Sub

表示子菜单。在嵌套使用时,用它替代根部件来创建子菜单。

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

defaultValue
string

The value of the menu item that should be active when initially rendered.

Use when you do not need to control the value state.

modelValue
string

The controlled value of the sub menu item to activate. Can be used as v-model.

orientation
'horizontal'
'vertical' | 'horizontal'

The orientation of the menu.

EmitPayload
update:modelValue
[value: string]

Event handler called when the value changes.

Slots (default)Payload
modelValue
string

Current input values

Data AttributeValue
[data-orientation]"vertical" | "horizontal"

List

包含顶级菜单项。

PropDefaultType
as
'ul'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

Data AttributeValue
[data-orientation]"vertical" | "horizontal"

Item

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

PropDefaultType
as
'li'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

value
string

A unique value that associates the item with an active value when the navigation menu is controlled.

This prop is managed automatically when uncontrolled.

Trigger

用于切换内容的按钮。

PropDefaultType
as
'button'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

disabled
boolean

When true, prevents the user from interacting with item

Data AttributeValue
[data-state]"open" | "closed"
[data-disabled]Present when disabled

Content

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

tip
Built with Presence component - supports any animation techniques while maintaining access to presence emitted events.
PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

disableOutsidePointerEvents
boolean

When true, hover/focus/click interactions will be disabled on elements outside the DismissableLayer. Users will need to click twice on outside elements to interact with them: once to close the DismissableLayer, and again to trigger the element.

forceMount
boolean

Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries.

EmitPayload
escapeKeyDown
[event: KeyboardEvent]

Event handler called when the escape key is down. Can be prevented.

focusOutside
[event: FocusOutsideEvent]

Event handler called when the focus moves outside of the DismissableLayer. Can be prevented.

interactOutside
[event: PointerDownOutsideEvent | FocusOutsideEvent]

Event handler called when an interaction happens outside the DismissableLayer. Specifically, when a pointerdown event happens outside or focus moves outside of it. Can be prevented.

pointerDownOutside
[event: PointerDownOutsideEvent]

Event handler called when a pointerdown event happens outside of the DismissableLayer. Can be prevented.

Data AttributeValue
[data-state]"open" | "closed"
[data-motion]"to-start" | "to-end" | "from-start" | "from-end"
[data-orientation]"vertical" | "horizontal"

导航链接。

PropDefaultType
active
boolean

Used to identify the link as the currently active page.

as
'a'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

EmitPayload
select
[payload: CustomEvent<{ originalEvent: Event; }>]

Event handler called when the user selects a link (via mouse or keyboard).

Calling event.preventDefault in this handler will prevent the navigation menu from closing when selecting that link.

Data AttributeValue
[data-active]Present when active

Indicator

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

tip
Built with Presence component - supports any animation techniques while maintaining access to presence emitted events.
PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

forceMount
boolean

Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries.

Data AttributeValue
[data-state]"visible" | "hidden"
[data-orientation]"vertical" | "horizontal"
CSS VariableDescription
--reka-navigation-menu-indicator-size
指示器的尺寸。
--reka-navigation-menu-indicator-position
指示器的位置

Viewport

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

tip
Built with Presence component - supports any animation techniques while maintaining access to presence emitted events.
PropDefaultType
align
'center'
'start' | 'center' | 'end'

Placement of the viewport for css variables (--reka-navigation-menu-viewport-left, --reka-navigation-menu-viewport-top).

as
'div'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

forceMount
boolean

Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries.

Data AttributeValue
[data-state]"visible" | "hidden"
[data-orientation]"vertical" | "horizontal"
CSS VariableDescription
--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 将在此处渲染 -->
    <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</NavigationMenu极客大学Content>
      </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,
  NavigationMenu极客大学Viewport,
} 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'

// 如果使用 `vue-router`,RouterLink 应默认注入
</script>

<template>
  <NavigationMenuRoot>
    <NavigationMenu极客大学List>
      <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 的披露式导航菜单示例。

链接使用和 aria-current

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

键盘交互

KeyDescription
SpaceEnter
当焦点在 NavigationMenuTrigger 上时,打开内容。
Tab
将焦点移动到下一个可聚焦元素。
ArrowDown
当为 horizontal 且焦点在打开的 NavigationMenuTrigger 上时,将焦点移动到 NavigationMenuContent 中。
将焦点移动到下一个 NavigationMenuTriggerNavigationMenuLink
ArrowUp
将焦点移动到上一个 NavigationMenuTrigger极客大学NavigationMenuLink
ArrowRightArrowLeft
当为 vertical 且焦点在打开的 NavigationMenuTrigger 上时,将焦点移动到其 NavigationMenuContent 中。
将焦点移动到下一个 / 上一个 NavigationMenuTrigger NavigationMenuLink
HomeEnd
将焦点移动到第一个/最后一个 NavigationMenu.TriggerNavigationMenu.Link
Esc
关闭打开的 NavigationMenu.Content 并将焦点移动到其 NavigationMenu.Trigger