aria-expanded 必须绑定在触发按钮上而非面板容器,正确结构为标题内容,且需同步dom状态、属性与视觉反馈。

aria-expanded 必须绑定在
最常踩的坑是把 aria-expanded 写在折叠内容的 <div> 上。它只对触发交互的按钮有意义——屏幕阅读器靠这个属性判断“用户点了这个按钮后,内容是不是展开了”。写错位置等于没写。
<p>正确结构必须是:</p>
<pre class="brush:php;toolbar:false;"><button id="toggle-1" aria-controls="panel-1" aria-expanded="false">标题</button>
<div id="panel-1" role="region">内容</div></pre>
<ul>
<li>
<code>button 要有 id,否则 aria-controls 指向失效
panel-1 的 id 必须和 aria-controls 值完全一致(大小写、拼写、连字符都不能错)<div role="button"> 代替 <code><button></button>:它不响应空格/回车,也不自动聚焦,可访问性直接崩掉
切换时必须用 setAttribute('aria-expanded', 'true') 或 'false'
不能靠 CSS 类名变化“假装”状态变了,也不能直接赋布尔值:btn.ariaExpanded = true 是无效写法,IE 和旧 Safari 完全不认。
每次点击后,得先改 DOM 状态(比如加 hidden 属性或切 height),再立刻同步 aria-expanded:
- 展开面板时:
btn.setAttribute('aria-expanded', 'true') - 收起面板时:
btn.setAttribute('aria-expanded', 'false') - 推荐用
btn.toggleAttribute('aria-expanded', isOpen)(现代浏览器支持,自动处理增删) - 判断当前状态别用
btn.ariaExpanded === true,要用btn.getAttribute('aria-expanded') === 'true'—— 因为它是字符串属性,不是 JS 布尔
状态必须和视觉/可访问性状态严格对齐
如果面板实际还藏着,aria-expanded 却设成了 "true",屏幕阅读器就会说“已展开”,但用户既看不到内容,也无法聚焦其中元素——这是严重语义断裂。
- 隐藏内容优先用
hidden属性,或aria-hidden="true"+display: none;别只用visibility: hidden或opacity: 0,这些方式内容仍在可访问树里 - 更新
aria-expanded的时机要卡准:等 CSS 过渡完成(比如监听transitionend)或 DOM 渲染确认后再设,否则 Safari + VoiceOver 可能静默不播报 - 图标旋转、加减号切换这些视觉反馈,必须和
aria-expanded同步操作——比如同时执行btn.classList.add('is-open')和btn.setAttribute('aria-expanded', 'true')
原生 / 能省掉 90% 手动同步逻辑
能省掉 90% 手动同步逻辑
除非你有强定制需求(比如特定动画、复杂嵌套、IE 兼容要求),否则直接用 <details></details> —— 它自动处理 aria-expanded、aria-controls、键盘焦点、空格/回车触发,所有主流浏览器(Chrome 12+、Firefox 49+、Safari 6+、Edge 79+)都支持。
- 错误做法:
<div onclick="toggle()">... —— 直接丢掉键盘可访问性和读屏器感知 <li>正确姿势:<code><details><summary>标题</summary>内容</details>,然后用 CSS 覆盖默认箭头样式 - 如果非要手写 JS 控制,复杂点不在“怎么设属性”,而在于每次交互都要检查真实 DOM 高度/hidden 状态、更新属性、同步 class、保障焦点回到按钮——漏掉任意一环,对屏幕阅读器用户就是不可用的











