aria-haspopup="menu"仅声明弹出类型,不说明“有列表”,必须配合role="menu"、role="menuitem"及动态aria-expanded才能让屏幕阅读器准确传达菜单结构与状态。

aria-haspopup="menu" 本身不自动告知“有列表”
视障用户依赖屏幕阅读器解析 aria-haspopup 的语义,但仅设为 "menu" 时,多数主流屏幕阅读器(如 NVDA、VoiceOver)只朗读“has popup”或“menu”,**不会主动说明“下面有列表项”或“可展开选项列表”**。这不是 bug,而是 WAI-ARIA 规范的设计:它只声明弹出类型,不描述内容结构。
必须配合 role="menu" 和子元素的 role="menuitem"
要让屏幕阅读器明确传达“这是一个可操作的菜单列表”,需在弹出容器及其子项上补全语义链:
- 触发按钮保持
aria-haspopup="menu"和aria-expanded(动态更新) - 弹出容器设
role="menu",且**必须是role="menuitem"的直接父级** - 每个可选项目用
role="menuitem"(不能用role="option"或普通li) - 若菜单项含图标或仅用符号,需加
aria-label或视觉隐藏文本,否则屏幕阅读器可能跳过或误读
示例片段:
<button aria-haspopup="menu" aria-expanded="false">文件</button> <div role="menu" aria-hidden="true"> <button role="menuitem">新建</button> <button role="menuitem">打开</button> <hr role="separator"> <button role="menuitem">退出</button> </div>
aria-expanded 是关键状态同步点
用户无法凭空知道菜单是否已展开,aria-expanded 的值("true"/"false")是屏幕阅读器判断“当前是否可浏览列表”的唯一依据。常见错误:
立即学习“前端免费学习笔记(深入)”;
- 只初始化
aria-expanded="false",但点击后没同步更新 —— 屏幕阅读器仍认为菜单关闭,不会进入菜单导航模式 - 用 CSS
display: none隐藏菜单时,忘记同步设aria-hidden="true"—— 可能导致焦点陷阱或重复朗读 - 菜单通过
transform或opacity动画隐藏,但未及时切换aria-expanded—— 状态与视觉不同步,用户操作失序
别指望 aria-label 替代结构语义
有人试图在按钮上加 aria-label="文件菜单,包含新建、打开、退出",这看似“说清楚了”,但实际效果差:
- 屏幕阅读器通常只朗读一次
aria-label,后续用户用方向键浏览时,无法感知列表结构和项数 - 键盘导航(如按 ↓ 进入下一项)失效 —— 没有
role="menu"和role="menuitem",阅读器不启用菜单导航模式 - 部分阅读器会忽略冗长
aria-label,或截断朗读,反而造成信息缺失
真正可靠的方式,是让 DOM 结构本身承载语义,而不是靠文本描述“骗过”辅助技术。
菜单交互路径是否顺畅,取决于角色、状态、父子关系三者是否严格对齐。漏掉任意一环,视障用户就可能卡在“知道有菜单,但找不到怎么进、里面有什么、怎么选”。



















