aria-expanded 必须由 javascript 动态控制以准确反映真实展开状态,初始值、切换逻辑、框架绑定和测试验证均需严格同步,否则会导致读屏器误报和可访问性断裂。

aria-expanded 应该由 JavaScript 控制,不能靠 class 或 CSS 切换
这个属性必须反映真实展开状态,浏览器和读屏器只认它的布尔值,不关心你有没有加 active 类或 display: block。手动写死 aria-expanded="true" 在 HTML 里是无效的——折叠时它还是 true,读屏器会误报。
- 每次点击按钮后,必须用 JS 显式设置
button.setAttribute('aria-expanded', 'true')或'false' - 不要依赖 CSS 类名推断状态,比如
if (panel.classList.contains('open'))再去设属性——容易漏掉初始状态或异步切换 - 初始化时就要同步:如果面板默认展开,
button的aria-expanded必须初始为"true",否则首次读屏就错
toggle 操作中要同时更新 button 和 panel 的状态
常见错误是只改了 panel 的 hidden 或 aria-hidden,忘了同步 button 的 aria-expanded。两者必须严格配对,否则语义断裂。
- 推荐统一用一个布尔变量管理状态,比如
let isExpanded = false,点击时先取反再应用到两边 -
button上建议同时设aria-controls(指向 panel 的 id),这样读屏器才能关联两者 - 避免直接操作
style.display,优先用hidden属性或aria-hidden,它们天然和aria-expanded协同
button.addEventListener('click', () => {
isExpanded = !isExpanded;
button.setAttribute('aria-expanded', String(isExpanded));
panel.setAttribute('hidden', String(!isExpanded));
});
React/Vue 等框架里别用字符串插值写 aria-expanded
像 aria-expanded="{isOpen}" 这种写法在 Vue 或 JSX 中看似简洁,但实际输出可能是 aria-expanded="true" 或 aria-expanded="false",而 WAI-ARIA 规范要求该属性**只接受字面量 "true" 或 "false" 字符串**,布尔值 true/false 会被序列化成 "true"/""(空字符串),后者等价于 false —— 但可访问性检测工具会报错。
- Vue 模板中写
:aria-expanded="isOpen.toString()" - JSX 中写
aria-expanded={String(isOpen)} - 不要写
aria-expanded={isOpen},React 会把false渲染成无该属性,导致缺失
测试时重点检查三个时刻的状态一致性
光看页面效果没用,得验证 DOM 层级上三者的实时对应关系:按钮的 aria-expanded、面板的 hidden(或 aria-hidden)、以及视觉是否匹配。最容易出问题的是动画过渡期间——比如用 max-height 做渐变展开时,aria-expanded 必须在动画开始前就更新,不能等 transitionend 才设。
- 用浏览器开发者工具的“Accessibility”面板,选中 button,看
aria-expanded是否实时变化 - 用 VoiceOver 或 NVDA 打开页面,按
Ctrl+Alt+Arrow导航,确认折叠/展开时播报一致 - 手动触发两次点击,检查
aria-expanded是否在"true"和"false"之间准确翻转,而不是卡在某一边
aria-expanded 当成“锦上添花”,结果在组件复用或服务端渲染时,初始值没对齐、状态没绑定、框架绑定写错类型,一整套可访问性逻辑就断了。它不是装饰属性,是控制流的一部分。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











