导航菜单
功能特点
- 支持受控或非受控模式。
- 灵活的布局结构,支持标签焦点管理。
- 支持子菜单。
- 可选的活动项指示器。
- 完整的键盘导航支持。
- 暴露 CSS 变量以实现高级动画效果。
- 支持自定义时序。
安装
通过命令行安装该组件。
$ npm add reka-ui结构
导入所有部件并进行组合。
<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
包含导航菜单的所有部件。
| Prop | Default | Type |
|---|---|---|
as | 'nav' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
defaultValue | stringThe 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 | numberThe 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 | |
disableClickTrigger | false | booleanIf |
disableHoverTrigger | false | booleanIf |
disablePointerLeaveClose | booleanIf | |
modelValue | stringThe controlled value of the menu item to activate. Can be used as | |
orientation | 'horizontal' | 'vertical' | 'horizontal'The orientation of the menu. |
skipDelayDuration | 300 | numberHow much time a user has to enter another trigger without incurring a delay again. |
unmountOnHide | true | booleanWhen |
| Emit | Payload |
|---|---|
update:modelValue | [value: string]Event handler called when the value changes. |
| Slots (default) | Payload |
|---|---|
modelValue | stringCurrent input values |
| Data Attribute | Value |
|---|---|
[data-orientation] | "vertical" | "horizontal" |
Sub
表示子菜单。在嵌套使用时,用它替代根部件来创建子菜单。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
defaultValue | stringThe value of the menu item that should be active when initially rendered. Use when you do not need to control the value state. | |
modelValue | stringThe controlled value of the sub menu item to activate. Can be used as | |
orientation | 'horizontal' | 'vertical' | 'horizontal'The orientation of the menu. |
| Emit | Payload |
|---|---|
update:modelValue | [value: string]Event handler called when the value changes. |
| Slots (default) | Payload |
|---|---|
modelValue | stringCurrent input values |
| Data Attribute | Value |
|---|---|
[data-orientation] | "vertical" | "horizontal" |
List
包含顶级菜单项。
| Prop | Default | Type |
|---|---|---|
as | 'ul' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
| Data Attribute | Value |
|---|---|
[data-orientation] | "vertical" | "horizontal" |
Item
顶级菜单项,包含链接或触发器与内容的组合。
| Prop | Default | Type |
|---|---|---|
as | 'li' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
value | stringA unique value that associates the item with an active value when the navigation menu is controlled. This prop is managed automatically when uncontrolled. |
Trigger
用于切换内容的按钮。
| Prop | Default | Type |
|---|---|---|
as | 'button' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-disabled] | Present when disabled |
Content
包含与每个触发器相关联的内容。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disableOutsidePointerEvents | booleanWhen | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| Emit | Payload |
|---|---|
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 |
interactOutside | [event: PointerDownOutsideEvent | FocusOutsideEvent]Event handler called when an interaction happens outside the |
pointerDownOutside | [event: PointerDownOutsideEvent]Event handler called when a |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-motion] | "to-start" | "to-end" | "from-start" | "from-end" |
[data-orientation] | "vertical" | "horizontal" |
Link
导航链接。
| Prop | Default | Type |
|---|---|---|
active | booleanUsed to identify the link as the currently active page. | |
as | 'a' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
| Emit | Payload |
|---|---|
select | [payload: CustomEvent<{ originalEvent: Event; }>]Event handler called when the user selects a link (via mouse or keyboard). Calling |
| Data Attribute | Value |
|---|---|
[data-active] | Present when active |
Indicator
一个可选的指示器元素,渲染在列表下方,用于高亮当前活动的触发器。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| Data Attribute | Value |
|---|---|
[data-state] | "visible" | "hidden" |
[data-orientation] | "vertical" | "horizontal" |
| CSS Variable | Description |
|---|---|
--reka-navigation-menu-indicator-size | 指示器的尺寸。 |
--reka-navigation-menu-indicator-position | 指示器的位置 |
Viewport
一个可选的视口元素,用于在列表之外渲染活动内容。
| Prop | Default | Type |
|---|---|---|
align | 'center' | 'start' | 'center' | 'end'Placement of the viewport for css variables |
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| Data Attribute | Value |
|---|---|
[data-state] | "visible" | "hidden" |
[data-orientation] | "vertical" | "horizontal" |
| CSS Variable | Description |
|---|---|
--reka-navigation-menu-viewport-width | 视口在显示/隐藏时的宽度,根据活动内容计算得出 |
--reka-navigation-menu-viewport-height | 视口在显示/隐藏时的高度,根据活动内容计算得出 |
示例
垂直布局
通过使用 orientation 属性可以创建垂直菜单。
<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 结构或需要灵活性来实现高级动画时非常有用。 标签焦点将自动维护。
<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 时非常有用。
<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>/* 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。
<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"。 这将确保保持可访问性和一致的键盘控制:
<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; 结合使用,可以在项之间移动时创建平滑的重叠动画效果。
<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>/* 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 角色。 这是因为 menu 和 menubars 的行为类似于本机操作系统菜单,最常见于桌面应用程序窗口中,因此它们具有复合焦点管理和首字符导航等复杂功能。
这些功能通常对于网站导航来说是不必要的,在最坏的情况下可能会让熟悉既定网站模式的用户感到困惑。
有关更多信息,请参阅 W3C 的披露式导航菜单示例。
链接使用和 aria-current
在菜单中的所有导航链接中使用 NavigationMenuLink 非常重要,这不仅适用于主列表,也适用于通过 NavigationMenuContent 渲染的任何内容。 这将确保一致的键盘交互和可访问性,同时还提供 active 属性来设置 aria-current 和活动样式。 有关与第三方路由组件一起使用的更多信息,请参阅此示例。
键盘交互
| Key | Description |
|---|---|
SpaceEnter |
当焦点在 NavigationMenuTrigger 上时,打开内容。
|
Tab | 将焦点移动到下一个可聚焦元素。 |
ArrowDown |
当为 horizontal 且焦点在打开的
NavigationMenuTrigger 上时,将焦点移动到
NavigationMenuContent 中。
将焦点移动到下一个 NavigationMenuTrigger 或
NavigationMenuLink。
|
ArrowUp |
将焦点移动到上一个 NavigationMenuTrigger 或
极客大学NavigationMenuLink。
|
ArrowRightArrowLeft |
当为 vertical 且焦点在打开的
NavigationMenuTrigger 上时,将焦点移动到其
NavigationMenuContent 中。
将焦点移动到下一个 / 上一个 NavigationMenuTrigger 或 NavigationMenuLink。
|
HomeEnd |
将焦点移动到第一个/最后一个 NavigationMenu.Trigger 或
NavigationMenu.Link。
|
Esc |
关闭打开的 NavigationMenu.Content 并将焦点移动到其
NavigationMenu.Trigger。
|
