aria-controls 必须精确指向抽屉容器的唯一 id,且需同步更新 aria-expanded、aria-hidden 并确保焦点可达与渲染稳定,任一环节缺失都将导致可访问性失效。

aria-controls 必须指向抽屉容器的唯一 ID
它不是描述性文字,而是 DOM 引用。如果写 aria-controls="drawer",页面里就必须存在一个 id="drawer" 的元素,且不能有第二个同名 ID。拼错、大小写不一致(drawer vs Drawer)、或 ID 被框架动态覆盖(如 React key 重用导致重复 ID),都会让屏幕阅读器静默忽略该关系。
- 检查 DOM 是否真实存在:
document.getElementById('drawer')返回null就说明绑定失败 - 多个按钮控制同一个抽屉时,可空格分隔:
aria-controls="drawer main-nav" - 不要用 class 名、CSS 选择器或变量插值代替 ID,
aria-controls=".drawer"或aria-controls={drawerId}(未求值)完全无效
抽屉容器必须是语义清晰的可访问元素
aria-controls 不要求目标元素带额外 role,但必须具备明确语义和可访问上下文。推荐用 <nav id="drawer"></nav> 或 <div id="drawer" role="region">,而不是裸 <code><div id="drawer">。若抽屉是模态导航,加 <code>role="navigation" 可增强意图表达;若是临时操作面板,role="region" 更通用。
- 避免在抽屉容器上写
aria-expanded——它只属于触发按钮 - 抽屉初始隐藏时,建议同步设
aria-hidden="true";展开后移除该属性 - 确保抽屉有
tabindex="-1",以便 JS 调用.focus()时能获得焦点
JS 切换状态时必须同步更新 aria-expanded 和 aria-hidden
aria-controls 本身不驱动任何行为,点击按钮后,JS 必须显式做三件事:更新按钮的 aria-expanded、切换抽屉的 aria-hidden、并调用 drawerElement.focus()。漏掉任意一项,键盘用户或屏幕阅读器就会卡住。
- 展开逻辑示例:
button.setAttribute('aria-expanded', 'true');<br>drawer.setAttribute('aria-hidden', 'false');<br>drawer.focus(); - 收起逻辑示例:
button.setAttribute('aria-expanded', 'false');<br>drawer.setAttribute('aria-hidden', 'true'); - 别依赖
display: none自动隐去可访问性——它会让元素彻底退出可访问树,aria-hidden="true"才是标准做法
移动端 Safari 的 fixed + transform 渲染 bug 会干扰 ID 绑定效果
即使 aria-controls 指向正确 ID、状态也同步了,iOS Safari 仍可能因渲染异常导致焦点丢失或读屏器跳过抽屉内容。这不是绑定问题,而是底层渲染层错乱引发的可访问性断裂。
- 抽屉根容器必须设
overflow-y: auto和固定高度(如max-height: 80vh),不能靠height: 100% - 避免在抽屉内部使用
position: sticky,Safari 会失效 - 真机测试比模拟器更关键——特别是 iOS 16+ 的 viewport 缩放行为会影响
fixed容器定位精度
aria-controls 就只是个摆设。











