aria-controls 和 aria-expanded 必须协同使用:前者通过唯一 id 关联按钮与菜单容器,后者在按钮上实时同步 true/false 展开状态;菜单需设 role、aria-labelledby 和 aria-hidden,并禁用 aria-expanded。

aria-controls 和 aria-expanded 是一对必须协同工作的 ARIA 属性,用于建立按钮与下拉菜单之间的语义关联,并准确传达当前展开状态。单独使用任何一个都无法满足无障碍基本要求。
aria-controls 要指向菜单容器的唯一 id
它不是装饰性属性,而是明确告诉屏幕阅读器“这个按钮控制的是哪个元素”。必须确保:
- 触发按钮(如
<button></button>)上写aria-controls="menu-id" - 对应下拉菜单容器有完全匹配的
id="menu-id" - 该 id 在整个页面中唯一,且不能动态生成后丢失或重复
- 按钮自身最好也有
id,以便菜单通过aria-labelledby反向关联
aria-expanded 必须加在按钮上,且实时同步视觉状态
它的值只能是 "true" 或 "false",反映菜单此刻是否可见。关键细节:
- 只加在按钮上,绝不能加在菜单容器里
- 初始值要和菜单默认状态一致(例如菜单默认隐藏,按钮就设
aria-expanded="false") - 每次点击后,必须用
button.setAttribute('aria-expanded', 'true')或'false'显式更新 - 不能依赖 CSS 类切换、
hidden属性变更或 React/Vue 的 state 更新来替代 DOM 属性操作
菜单容器需补充必要角色和属性
仅靠 aria-controls 和 aria-expanded 不足以构成完整语义链,菜单本身也要配合:
- 添加
role="menu"(如果是菜单)或role="listbox"(如果是选择列表),避免用泛用的div - 设置
aria-labelledby="按钮-id",让屏幕阅读器知道“这是谁弹出的内容” - 隐藏时建议加
aria-hidden="true",显示时移除;若已用hidden或display: none,此属性可省略但显式声明更稳妥 - 避免对菜单容器设置
aria-expanded—— 这是常见错误,会导致辅助技术完全忽略状态变化
推荐组合写法示例
一个符合 WCAG 2.1 的最小可行结构:
<button id="nav-toggle" aria-haspopup="menu" aria-expanded="false" aria-controls="main-menu"> 导航菜单 </button>
JavaScript 切换时,务必先更新菜单的可见性(如 menu.hidden = false),再执行 button.setAttribute('aria-expanded', 'true') —— 顺序不能颠倒。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











