aria-controls 的值必须是页面中真实存在的唯一 id 字符串,需与目标元素 id 完全一致,并配合 aria-expanded 同步更新;目标区域须设 role 和 aria-hidden,禁用 display: none;动态内容优先用 aria-labelledby 反向关联。

aria-controls 的值必须是真实存在的 ID 字符串
它不是 class 名、CSS 选择器,也不是 JS 变量名。写成 aria-controls=".panel" 或 aria-controls={panelId}(未求值)直接失效。必须是纯字符串,且页面中真有对应 id="panel" 的元素。
常见错误包括:
- ID 拼错:按钮写
aria-controls="nav-menu",但目标是id="nav_menu"(下划线 vs 短横线) - ID 重复:多个区域用了同一个
id="dropdown",辅助技术只认第一个 - ID 尚未挂载:目标
div是 JS 动态插入的,但按钮在 DOM 加载时就设置了aria-controls="dynamic-panel",此时 ID 还不存在
目标区域必须有 role 和唯一 ID,不能仅靠 display: none 隐藏
aria-controls 不管显隐,只声明关系。目标区域得自己承担语义和可访问性责任:
- 必须设
id,且与aria-controls值完全一致 - 建议加
role="region"(折叠面板)或role="menu"(下拉菜单),不加也行,但缺 role 会让键盘导航失效 - 隐藏时用
aria-hidden="true"+ 视觉隐藏(如clip-path: inset(100%)),别用display: none或hidden属性——它们会让内容彻底从可访问性树中消失
必须和 aria-expanded 同步更新,且 JS 手动控制
aria-controls 和 aria-expanded 是一对齿轮,不能只转一个:
- 点击按钮展开时,要同时:
→ 设置按钮aria-expanded="true"
→ 设置目标区域aria-hidden="false"
→ 确保目标区域在视觉上可见(CSS 控制) - 收起时反过来:
→ 按钮aria-expanded="false"
→ 目标aria-hidden="true"
→ 视觉隐藏 - 漏掉任一环节,屏幕阅读器就会朗读“已展开”,但用户按 Enter 没反应;或朗读“已收起”,实际内容却还显示着
动态加载目标区域时,优先用 aria-labelledby 反向关联
如果目标 div 是 Tab 切换后才插入 DOM,或者由框架(React/Vue)按需渲染,硬写 aria-controls 很容易断链。更稳妥的做法是反向操作:
- 让目标区域自己带
aria-labelledby="trigger-id",指向触发按钮的 ID - 按钮仍保留
role="button"和aria-expanded,但可省略aria-controls - 这样即使目标晚渲染,只要它挂载时设置了
aria-labelledby,读屏器就能建立关联
真正难的不是写对属性,而是确保 JS 状态、DOM 结构、CSS 渲染、ARIA 属性四者始终咬合。哪怕只有一处不同步,对屏幕阅读器用户来说,就是整个交互消失了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











