仅添加 role="menu" 和 role="menuitem" 不足以实现可访问下拉菜单,必须配合 JS 实现焦点管理、键盘导航(↑/↓/Enter/Esc)、aria-expanded 同步、嵌套子菜单层级关联及正确禁用状态处理,否则视障用户无法操作。

role="menu" 和 role="menuitem" 不是纯语义装饰,必须配合键盘交互逻辑
仅加 role="menu" 和 role="menuitem" 不会让下拉菜单真正可访问。屏幕阅读器会按菜单模式读出结构,但焦点管理、键盘行为(如 ↑/↓ 切换、Enter 激活、Esc 关闭)全靠 JS 实现。没写对应逻辑,视障用户卡在第一个项动不了。
常见错误现象:role="menu" 容器内所有 role="menuitem" 默认不可聚焦(没 tabindex),用键盘打不开;或者打开后按方向键无响应,焦点跳到页面其他地方。
-
role="menu"必须直接包裹role="menuitem"或role="menuitemradio"/role="menuitemcheckbox",不能混用div或无角色元素 - 每个
role="menuitem"需设tabindex="-1"(初始不进 Tab 流,JS 控制聚焦) - 菜单展开时,需用 JS 把焦点移到第一个
menuitem,并监听 ArrowDown/ArrowUp - 父级触发按钮(如“更多”按钮)要设
aria-haspopup="true"和aria-expanded="false/true"
menuitem 的 aria-disabled 与 disabled 属性不能混用
disabled 是 HTML 原生属性,对 role="menuitem" 无效 —— 它只作用于表单控件(button、input 等)。直接写 <div role="menuitem" disabled>导出</div>,屏幕阅读器仍会把它当可用项读出来,键盘也能聚焦,纯属误导。
正确做法是用 aria-disabled="true",并配 CSS 禁用视觉反馈(比如 opacity: 0.5; pointer-events: none;)。
立即学习“前端免费学习笔记(深入)”;
-
aria-disabled="true"会让屏幕阅读器播报“已禁用”,且不会响应键盘激活(Enter/Space) - 如果
menuitem实际是button元素,可以用原生disabled,此时aria-disabled反而冗余 - 别忘了同步更新
aria-expanded和菜单容器的显示/隐藏状态,否则屏幕阅读器读到“已展开”但看不到内容
嵌套子菜单(二级菜单)必须用 menuitem + menu 组合,且层级关系要显式声明
下拉菜单带“设置 → 显示偏好”这种二级结构时,不能只靠缩进或 CSS 实现。必须用 role="menuitem" 表示父项,并在其内部放一个 role="menu" 容器,再放子项 —— 同时用 aria-haspopup="menu" 和 aria-controls="submenu-id" 关联。
容易踩的坑:子菜单 role="menu" 没设 id,或 aria-controls 值和实际 ID 不匹配,导致屏幕阅读器无法建立父子导航关系。
- 子菜单
role="menu"应放在父menuitem的相邻位置(DOM 顺序),不要用绝对定位藏到远处 - 子菜单首次展开时,焦点必须落到第一个子
menuitem,而非停留在父项 - 从子菜单按 Esc 应关闭当前子菜单并返回父菜单;再按一次 Esc 才关闭整个菜单
- 横向二级菜单(如顶部导航栏)需额外处理 ArrowRight/ArrowLeft,但垂直下拉通常只需上下键
role="menu" 在现代 Web 中适用场景其实很窄
除非你做的是桌面应用风格的原生菜单(比如 IDE 工具栏、编辑器右键菜单),否则大多数网站下拉导航更适合用 role="navigation" + aria-expanded + aria-controls。WAI-ARIA 1.2 明确建议:网页导航结构优先用语义化 HTML(nav、ul、a)+ ARIA 增强,而不是强行套用 menu 角色。
原因很实在:浏览器对 menu 的键盘支持不一致(Safari 对 Arrow 键支持弱),且多数屏幕阅读器用户更熟悉导航栏模式而非操作系统菜单模式。
- 电商网站“分类”下拉、后台系统“用户”下拉,都属于导航场景,不是命令菜单,别硬套
role="menu" - 如果用了
role="menu",就必须实现完整菜单键盘协议(包括循环聚焦、Home/End 键等),工作量远超必要 - 测试时重点看 VoiceOver(macOS/iOS)和 NVDA(Windows)下方向键是否连贯,以及 JAWS 是否正确识别层级
role,而是让键盘焦点像鼠标一样自然地“知道该去哪”。很多团队花两小时调 ARIA 属性,却花两天修焦点跳转逻辑 —— 这才是真瓶颈。



















