角色 复合控件
menu
将容器标记为应用菜单 — 即「文件/编辑」风格菜单或上下文菜单的弹出层。没有对应的 HTML 等效元素。网站导航列表不是 menu;应使用包含链接 <ul> 的 <nav>。
何时使用
适用于应用风格的菜单 — 即从「文件」按钮弹出或通过右键点击触发的上下文菜单,支持方向键导航和按 Escape 关闭。子元素必须为 role="menuitem"、role="menuitemcheckbox"、role="menuitemradio" 或 role="separator"。
menu 角色属于应用语义。Web 上最常见的单一错误,就是将 role="menu" 用于网站导航 — 这会将键盘用户困在方向键导航中,错误地将链接宣告为菜单项,并破坏用户的心智模型。
经验法则:如果触发器是超链接且菜单项也是超链接,则它是导航,而非菜单。请使用 <nav> 和 <ul>。
menu 必须具有可访问名称 — 使用 aria-label 或 aria-labelledby 指向触发元素。
键盘与焦点规范
根据 APG 菜单模式:
- 触发按钮具有
aria-haspopup="menu"和aria-expanded。激活触发器会打开菜单并将焦点移至第一个 menuitem。 - 菜单内部:上/下方向键在菜单项之间移动焦点;Home/End 跳转到第一个/最后一个菜单项。
- 右方向键打开子菜单(如果聚焦项包含子菜单);左方向键返回父菜单。
- Enter 或 Space 激活聚焦项并关闭菜单。
- Escape 关闭菜单并将焦点返回触发器。
- 首字母快速跳转:输入字母可跳转到以该字母开头的下一个菜单项。
单一制表位:同一时刻只有一个 menuitem 的 tabindex="0",其余均为 -1。
常见错误
- 在
<nav>元素上使用role="menu"。应用语义会覆盖导航语义 — 屏幕阅读器不再将其宣告为导航地标。 - 触发器上缺少与菜单配对的
aria-haspopup="menu"。 - Escape 关闭菜单后未将焦点返回触发器,导致用户迷失位置。
- 菜单打开时焦点未移至第一个 menuitem — 用户不得不通过 Tab 键穿越空白区域。
- 子菜单仅在悬停时打开,不支持键盘触发。
示例
<button id="actions" aria-haspopup="menu" aria-expanded="false" aria-controls="actionsMenu">
Actions
</button>
<ul id="actionsMenu" role="menu" aria-labelledby="actions" hidden>
<li role="menuitem" tabindex="0">Edit</li>
<li role="menuitem" tabindex="-1">Duplicate</li>
<li role="separator"></li>
<li role="menuitem" tabindex="-1">Delete</li>
</ul>