aria-controls仅声明逻辑控制关系,不触发行为,需配合role、aria-expanded、JS状态同步及焦点管理才能实现可访问下拉菜单。

aria-controls 的作用不是控制显隐
很多人以为给按钮加 aria-controls="menu-id" 就能自动展开下拉菜单,其实它只是声明“这个元素逻辑上控制另一个元素”,不触发任何行为,也不影响 DOM 渲染或 JS 执行。是否展开、如何聚焦、键盘导航是否生效,全靠你自己写 JS 和 CSS 配合。
必须确保 ID 匹配且唯一
如果 aria-controls 指向的 ID 不存在、拼错、或页面中重复,屏幕阅读器会静默忽略该关系,用户完全感知不到控制逻辑。常见错误包括:
-
aria-controls="dropdown-menu"对应的菜单是id="dropdown_menu"(短横线 vs 下划线) - 多个下拉菜单用了同一个 ID,比如都叫
id="menu" - 菜单是动态渲染的,JS 插入时没同步设置 ID,或 ID 被 Vue/React key 机制意外覆盖
配合 role 和 aria-expanded 才算完整
单独用 aria-controls 几乎没意义。标准下拉组合至少要三要素齐备:
- 触发按钮:
role="button"+aria-expanded="false"+aria-controls="menu-id" - 菜单容器:
role="menu"+id="menu-id"+aria-hidden="true"(初始隐藏) - 菜单项:
role="menuitem",支持 ↑/↓ 导航和 Enter 激活
漏掉 aria-expanded,屏幕阅读器无法告知用户当前状态;漏掉 role="menu",键盘用户按 Tab 可能跳过整个菜单。
立即学习“前端免费学习笔记(深入)”;
JavaScript 必须手动同步状态
点击按钮时,你得同时做三件事:
- 切换菜单的
aria-hidden值(true/false) - 更新按钮的
aria-expanded("true"/"false") - 手动调用
menuElement.focus()或聚焦第一个menuitem,否则键盘用户无法进入菜单
别依赖 CSS display: none 或 visibility: hidden 隐藏菜单——它们会让屏幕阅读器直接跳过内容。要用 aria-hidden="true" + 视觉隐藏(如 clip-path 或 position: absolute; left: -9999px)配合。
最常被忽略的是焦点管理:菜单展开后不主动聚焦,等于把键盘用户挡在门外。而 aria-controls 本身不会帮你做这件事。



















