树形导航
概述
TreeNav 是一个数据驱动的水平导航栏,携带持久选择态:顶层条目横向排列,分支项默认悬停展开弹层,选中任意叶子后整条祖先链持续高亮。
STreeNav 是 STreeMenu 的横向版本:两者消费同一份树形 items 数据(TreeNavOptionData)并基于选中值派生高亮,区别在于 TreeNav 的分支渲染为 DropdownMenu 弹层而非折叠分组。逻辑与无障碍语义由 Aria TreeNavCompact 承载,样式通过 scv() 配方注入。
与 Menubar(瞬态"哪个菜单正打开"的命令菜单模型)不同,TreeNav 建模的是选择:打开弹层永远不会激活任何条目——只有叶子被选中才会。选中叶子的祖先链会持续携带高亮标记。
功能特性
- 持久选择态 — 绑定
modelValue(v-model)或用defaultValue设置初始值;弹层关闭后选择依然保留。 - 高亮派生 — 选中的叶子渲染
data-selected="true",其祖先链上的每一层渲染data-child-selected;派生复用共享树路径工具,任意深度均可正确传播。 - 悬停优先的弹层 — 分支弹层默认悬停展开(可用
trigger="click"覆盖),通过delayDuration/skipDelayDuration微调时序。 - 对齐 TreeMenu 的数据形态 — 一份
items数组(TreeNavOptionData:value/label/icon/children/href/to/disabled…)与 TreeMenu 的选项模型保持一致;徽标、标签、操作按钮等条目自定义能力通过插槽实现,不再依赖额外数据字段。 - 链接型顶层项 — 提供
href/to的顶层项渲染为链接,点击同时更新选择。 - 溢出折叠 — 开启
collapsible后,超宽的末尾顶层项自动合并进尾部"更多"弹层,始终贴合容器宽度;可通过moreLabel/moreIcon/moreProps/more-trigger插槽定制。 - 导航语义 — 根元素渲染
<nav>(可用as覆写);触发器的aria-haspopup/aria-expanded、Escape 关闭等语义继承自 DropdownMenu 层。 - 键盘导航 — 顶层是单一 roving Tab 停留点:←/→ 对所有条目一视同仁地漫游(跳过禁用项、首尾不循环),Home/End 跳转首尾条目;分支弹层的打开走显式按键——Enter/Space(原生按钮语义)或 ↓——弹层一旦打开,按键全部归属 Menu 机制。
- 逐项与整体禁用 —
item.disabled使单个条目惰性;栏级disabled一次禁用全部。 - 隐藏项 —
item.hidden将条目及其子树从导航栏与弹层中移除;子项全部隐藏的分支按普通叶子渲染。 - 六种尺寸与两种变体 —
size(xs…2xl)加variant="default"(浅色卡片底)或variant="nav"(纯净无框)。 - 插槽透传 —
item/item-leading/item-trailing/item-trigger-icon/item-link-icon同时透传到顶层与弹层内部。 - Aria 组合 — 无样式数据驱动用法可导入
@vean/aria/tree-nav;弹层选项复用MenuOptionsCompact。
用法
演示
选择态
Selected: email — opening a popup never marks a branch active; only selection does.
禁用
溢出折叠
API
Aria 层
6TreeNav
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
class | ClassValue | — | 树导航的根类。 |
size | ThemeSize | — | 组件的视觉尺寸。 |
ui | Partial<TreeNavUi> | — | 该组件的按槽类重写。 |
collapsible | boolean | false | 是否将顶层的溢出项目折叠到末尾的“更多”分支
弹出窗口中,以确保进度条始终能完全容纳在其容器内。 |
modelValue | string | — | 当前所选项的值。可作为 `v-model` 使用。 |
defaultValue | string | — | 初始渲染时应选中的项的值。 |
dir | Direction | — | 如适用,请标注组件的阅读方向。 |
trigger | DropdownMenuTriggerType | 'hover' | 分支弹出窗口的打开方式。
- `click`:点击触发器时,弹出窗口将打开。
- `hover`:将鼠标悬停在触发器上时,弹出窗口将打开。 |
delayDuration | number | 150 | 从指针进入分支触发器开始,直到
悬停模式下弹出窗口打开为止的时间间隔。 |
skipDelayDuration | number | 300 | 用户有多少时间可以触发另一个分支,而不会
再次产生延迟。 |
placement | Placement | 'bottom-start' | 分支弹出窗口相对于其触发点的首选位置。 |
showArrow | boolean | false | 分支弹出窗口是否显示箭头。 |
disabled | boolean | — | 整个导航栏是否被禁用。 |
linkProps | LinkExtraProps | — | 传递给链接项的 link 元素的属性。 |
itemProps | MenuItemProps | — | 转发至弹出项的 item 元素的属性。 |
groupLabelProps | MenuGroupLabelProps | — | 转发至弹出窗口组标签的 group label 元素的属性。 |
shortcutProps | MenuShortcutProps | — | 转发至弹出项快捷方式元素的属性。 |
separatorProps | MenuSeparatorProps | — | 传递给弹出式分隔符的分隔符元素的属性。 |
subTriggerProps | MenuSubTriggerProps | — | 转发给弹出窗口分支子触发器元素的属性。 |
subContentProps | MenuSubContentProps | — | 属性将转发至弹出窗口分支的子内容元素。 |
portalProps | MenuPortalProps | — | 转发至弹出窗口(popups)的 portal 元素的属性。 |
popupProps | MenuPopupProps | — | 转发给弹出窗口(popups)的弹出元素的属性。 |
triggerProps | DropdownMenuTriggerProps | — | 转发给分支弹出窗口触发元素的属性。 |
arrowProps | MenuArrowProps | — | 转发给弹出窗口箭头元素的属性。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
items | TreeNavOptionData<TreeNavBaseOptionData>[] | — | 顶级项目应渲染为可见条目。 |
moreItems | TreeNavOptionData<TreeNavBaseOptionData>[] | — | 项目已折叠至末尾的“更多”弹出窗口中。 |
moreLabel | string | 'More' | 尾部“more”触发器的标签。 |
moreIcon | IconValue | 'lucide:ellipsis' | 尾部“更多”触发器的图标。 |
moreProps | ButtonProps | — | 传递给末尾“更多”触发按钮的属性。 |
事件
| 事件名 | 参数 | 描述 |
|---|---|---|
update:modelValue | string | 当选定值发生变化时触发。 |
select | TreeNavOptionData<TreeNavBaseOptionData>, event: Event | 当选中某项时触发。 |
插槽
| 插槽名 | 参数 | 描述 |
|---|---|---|
item | { item: MenuOptionData<DefinedValue>; isTrigger?: boolean; } | 项插槽的自定义内容。 |
item-leading | { item: MenuOptionData<DefinedValue>; } | 用于项目引导槽位的自定义内容。 |
item-trailing | { item: MenuOptionData<DefinedValue>; } | 用于项目尾部槽位的自定义内容。 |
item-trigger-icon | { item: MenuOptionData<DefinedValue>; } | 项目触发图标槽位的自定义内容。 |
item-link-icon | { item: MenuOptionData<DefinedValue>; } | 项目链接图标槽位的自定义内容。 |
more-trigger | TreeNavMoreEntry | 若存在尾随的“更多”触发器,请提供相应自定义内容。 |
TreeNavCompact
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
collapsible | boolean | false | 是否将顶层的溢出项目折叠到末尾的“更多”分支
弹出窗口中,以确保进度条始终能完全容纳在其容器内。 |
modelValue | string | — | 当前所选项的值。可作为 `v-model` 使用。 |
defaultValue | string | — | 初始渲染时应选中的项的值。 |
dir | Direction | — | 如适用,请标注组件的阅读方向。 |
trigger | DropdownMenuTriggerType | 'hover' | 分支弹出窗口的打开方式。
- `click`:点击触发器时,弹出窗口将打开。
- `hover`:将鼠标悬停在触发器上时,弹出窗口将打开。 |
delayDuration | number | 150 | 从指针进入分支触发器开始,直到
悬停模式下弹出窗口打开为止的时间间隔。 |
skipDelayDuration | number | 300 | 用户有多少时间可以触发另一个分支,而不会
再次产生延迟。 |
placement | Placement | 'bottom-start' | 分支弹出窗口相对于其触发点的首选位置。 |
showArrow | boolean | false | 分支弹出窗口是否显示箭头。 |
disabled | boolean | — | 整个导航栏是否被禁用。 |
linkProps | LinkExtraProps | — | 传递给链接项的 link 元素的属性。 |
itemProps | MenuItemProps | — | 转发至弹出项的 item 元素的属性。 |
groupLabelProps | MenuGroupLabelProps | — | 转发至弹出窗口组标签的 group label 元素的属性。 |
shortcutProps | MenuShortcutProps | — | 转发至弹出项快捷方式元素的属性。 |
separatorProps | MenuSeparatorProps | — | 传递给弹出式分隔符的分隔符元素的属性。 |
subTriggerProps | MenuSubTriggerProps | — | 转发给弹出窗口分支子触发器元素的属性。 |
subContentProps | MenuSubContentProps | — | 属性将转发至弹出窗口分支的子内容元素。 |
portalProps | MenuPortalProps | — | 转发至弹出窗口(popups)的 portal 元素的属性。 |
popupProps | MenuPopupProps | — | 转发给弹出窗口(popups)的弹出元素的属性。 |
triggerProps | DropdownMenuTriggerProps | — | 转发给分支弹出窗口触发元素的属性。 |
arrowProps | MenuArrowProps | — | 转发给弹出窗口箭头元素的属性。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
items | TreeNavOptionData<TreeNavBaseOptionData>[] | — | 顶级项目应渲染为可见条目。 |
moreItems | TreeNavOptionData<TreeNavBaseOptionData>[] | — | 项目已折叠至末尾的“更多”弹出窗口中。 |
moreLabel | string | 'More' | 尾部“more”触发器的标签。 |
moreIcon | IconValue | 'lucide:ellipsis' | 尾部“更多”触发器的图标。 |
moreProps | ButtonProps | — | 传递给末尾“更多”触发按钮的属性。 |
事件
| 事件名 | 参数 | 描述 |
|---|---|---|
update:modelValue | string | 当选定值发生变化时触发。 |
select | TreeNavOptionData<TreeNavBaseOptionData>, event: Event | 当选中某项时触发。 |
插槽
| 插槽名 | 参数 | 描述 |
|---|---|---|
item | { item: MenuOptionData<DefinedValue>; isTrigger?: boolean; } | 项插槽的自定义内容。 |
item-leading | { item: MenuOptionData<DefinedValue>; } | 用于项目引导槽位的自定义内容。 |
item-trailing | { item: MenuOptionData<DefinedValue>; } | 用于项目尾部槽位的自定义内容。 |
item-trigger-icon | { item: MenuOptionData<DefinedValue>; } | 项目触发图标槽位的自定义内容。 |
item-link-icon | { item: MenuOptionData<DefinedValue>; } | 项目链接图标槽位的自定义内容。 |
more-trigger | TreeNavMoreEntry | 若存在尾随的“更多”触发器,请提供相应自定义内容。 |
TreeNavOptionCompact
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
item | TreeNavOptionData<T extends TreeNavBaseOptionData = TreeNavBaseOptionData> | — | 当前条目数据。 |
childSelected | boolean | — | 所选叶节点是否位于此分支子树中。 |
TreeNavOptionsCompact
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
items | TreeNavOptionData<TreeNavBaseOptionData>[] | — | 顶级项目应渲染为可见条目。 |
moreItems | TreeNavOptionData<TreeNavBaseOptionData>[] | — | 项目已折叠至末尾的“更多”弹出窗口中。 |
moreLabel | string | 'More' | 尾部“more”触发器的标签。 |
moreIcon | IconValue | 'lucide:ellipsis' | 尾部“更多”触发器的图标。 |
moreProps | ButtonProps | — | 传递给末尾“更多”触发按钮的属性。 |
插槽
| 插槽名 | 参数 | 描述 |
|---|---|---|
item | { item: MenuOptionData<DefinedValue>; isTrigger?: boolean; } | 项插槽的自定义内容。 |
item-leading | { item: MenuOptionData<DefinedValue>; } | 用于项目引导槽位的自定义内容。 |
item-trailing | { item: MenuOptionData<DefinedValue>; } | 用于项目尾部槽位的自定义内容。 |
item-trigger-icon | { item: MenuOptionData<DefinedValue>; } | 项目触发图标槽位的自定义内容。 |
item-link-icon | { item: MenuOptionData<DefinedValue>; } | 项目链接图标槽位的自定义内容。 |
more-trigger | TreeNavMoreEntry | 若存在尾随的“更多”触发器,请提供相应自定义内容。 |
TreeNavOverflow
TreeNavRoot
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
modelValue | string | — | 当前所选项的值。可作为 `v-model` 使用。 |
defaultValue | string | — | 初始渲染时应选中的项的值。 |
dir | Direction | — | 如适用,请标注组件的阅读方向。 |
trigger | DropdownMenuTriggerType | 'hover' | 分支弹出窗口的打开方式。
- `click`:点击触发器时,弹出窗口将打开。
- `hover`:将鼠标悬停在触发器上时,弹出窗口将打开。 |
delayDuration | number | 150 | 从指针进入分支触发器开始,直到
悬停模式下弹出窗口打开为止的时间间隔。 |
skipDelayDuration | number | 300 | 用户有多少时间可以触发另一个分支,而不会
再次产生延迟。 |
placement | Placement | 'bottom-start' | 分支弹出窗口相对于其触发点的首选位置。 |
showArrow | boolean | false | 分支弹出窗口是否显示箭头。 |
disabled | boolean | — | 整个导航栏是否被禁用。 |
linkProps | LinkExtraProps | — | 传递给链接项的 link 元素的属性。 |
itemProps | MenuItemProps | — | 转发至弹出项的 item 元素的属性。 |
groupLabelProps | MenuGroupLabelProps | — | 转发至弹出窗口组标签的 group label 元素的属性。 |
shortcutProps | MenuShortcutProps | — | 转发至弹出项快捷方式元素的属性。 |
separatorProps | MenuSeparatorProps | — | 传递给弹出式分隔符的分隔符元素的属性。 |
subTriggerProps | MenuSubTriggerProps | — | 转发给弹出窗口分支子触发器元素的属性。 |
subContentProps | MenuSubContentProps | — | 属性将转发至弹出窗口分支的子内容元素。 |
portalProps | MenuPortalProps | — | 转发至弹出窗口(popups)的 portal 元素的属性。 |
popupProps | MenuPopupProps | — | 转发给弹出窗口(popups)的弹出元素的属性。 |
triggerProps | DropdownMenuTriggerProps | — | 转发给分支弹出窗口触发元素的属性。 |
arrowProps | MenuArrowProps | — | 转发给弹出窗口箭头元素的属性。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
事件
| 事件名 | 参数 | 描述 |
|---|---|---|
update:modelValue | string | 当选定值发生变化时触发。 |
select | TreeNavOptionData<TreeNavBaseOptionData>, event: Event | 当选中某项时触发。 |
插槽
| 插槽名 | 参数 | 描述 |
|---|---|---|
default | — | 导航根节点内渲染的默认内容。 |
备注
各导航组件如何选型
| 组件 | 状态模型 | 方向 | 适用场景 |
|---|---|---|---|
SMenubar | 打开的菜单(modelValue) | 横向 | 应用命令菜单(File/Edit) |
SNavMenu | 高亮项(modelValue) | 横向 | 站点级导航 + 浮层面板 |
STreeMenu | 持久选择态 | 纵向 | 侧边栏导航 |
STreeNav | 持久选择态 | 横向 | 顶部导航栏 |
运行时注意事项
- 打开 ≠ 激活 — 弹层打开状态是 DropdownMenu 内部管理的瞬时 UI 态;只有叶子选中才更新
data-selected。不要沿用SMenubar的modelValue语义。 - 折叠后的高亮一致性 — 选中项被收进"更多"弹层后,其可见祖先仍显示
data-child-selected,因为派生始终基于完整的items列表。 - 折叠测量 — 与 Menubar 的溢出折叠一样,测量在挂载后基于真实布局进行;父容器请提供受限宽度(如
max-w-*/ 固定宽度)。首帧可能先全量渲染再收缩。 - 受控模式 — 提供
modelValue时内部只触发update:modelValue;非受控用法用defaultValue设定初始值。 - 键盘模型 — 弹层关闭时 ←/→(及 Home/End)通过 roving tabindex 在顶层漫游;分支触发器与其他条目一视同仁,方向键只移动、不打开弹层。打开弹层走 Enter/Space 或 ↓(焦点随后落入首个菜单项);弹层一旦打开,所有按键归属 Menu(弹层内 ↑/↓,Escape 关闭并归还焦点到触发器),hover 弹层在焦点离开触发器时自动收起。
FAQ
导航栏支持哪些键盘快捷键?
←/→ 在顶层条目间漫游(Home/End 跳转首尾)——分支触发器同样只是漫游目标,方向键只移动、不会打开弹层。弹层通过分支触发器上的 Enter/Space 或 ↓ 打开(焦点落入首个菜单项)。方向键从不改变选中项——选中只在显式激活(点击,或叶子上的 Enter/Space)时发生。
如何让顶层条目直接跳转而不是展开下拉?
给该条目设置 href 或 to——链接型条目渲染为 <a> 元素,点击同时更新选择。
如何精确控制哪个条目保持高亮?
绑定 v-model 即可。选中的值会与整棵 items 树比对:命中的叶子获得 data-selected,其祖先获得 data-child-selected。
为什么悬停分支不会让它变为激活?
打开弹层是瞬时状态而非选择——请在弹层内选中某个叶子。这与 STreeMenu 中"容器节点不携带 active 态"的行为一致。
导航栏放不下怎么办?
传入 collapsible。超出容器宽度的末尾条目会自动合并进"更多"弹层(默认文本 More),并在容器尺寸变化时自动重算。