必须用 setattribute 传字符串 "true"/"false" 更新 aria-expanded,禁用布尔赋值或非标准 ariaexpanded 属性;需基于 dom 实际状态(如 offsetheight > 0 且无 hidden)判断,配合 aria-controls 和 role="region",更新后立即 focus 按钮以确保 voiceover 正确播报。

点击时用 setAttribute 切换 aria-expanded 的字符串值
必须传字符串 "true" 或 "false",不能传布尔值 true/false。浏览器会把布尔值转成无引号的字面量(如 aria-expanded="true" 变成 aria-expanded="true" 看似一样,但实际是 aria-expanded="true" → aria-expanded="true",而布尔赋值可能变成 aria-expanded="true" 这种非标准写法),部分 AT(尤其是旧版 Safari VoiceOver)会忽略或误读。
正确做法是显式判断状态后调用 setAttribute:
button.addEventListener('click', () => {
const isExpanded = button.getAttribute('aria-expanded') === 'true';
const next = isExpanded ? 'false' : 'true';
button.setAttribute('aria-expanded', next);
});
- 别用
button.ariaExpanded = true—— 这是非标准属性,IE 和部分 Safari 版本不识别 - 别用
button.setAttribute('aria-expanded', isOpen)(isOpen是布尔变量)—— 会导致值为true而非"true" - 现代可选:用
button.toggleAttribute('aria-expanded', isOpen),自动处理增删,但需确认目标浏览器支持(Chrome 84+、Firefox 79+、Safari 15.4+)
切换前必须先确认真实 DOM 状态,不能只信 class 或变量
常见错误是 JS 里维护一个 isOpen 变量,但没和真实 DOM 对齐。比如 CSS 动画还没结束、hidden 属性没同步、或上一次操作被中断,都会让变量失真。
推荐用 DOM 实际表现判断,再设属性:
const panel = document.getElementById('panel-1');
const isActuallyVisible = panel.offsetHeight > 0 && !panel.hasAttribute('hidden');
button.setAttribute('aria-expanded', isActuallyVisible ? 'true' : 'false');
- 优先检查
offsetHeight或clientHeight> 0,比查display值更可靠(display: flex和block都能生效) - 如果用了
hidden属性,必须同时检查!panel.hasAttribute('hidden') - 避免只靠
classList.contains('is-open')—— class 可能残留,或动画中 class 已切但渲染未完成
Safari + VoiceOver 下不朗读?焦点回传是关键
即使 aria-expanded 正确更新,Safari 的 VoiceOver 仍可能静默,尤其在快速连点或按钮无焦点时。这不是代码错,而是 WebKit 的通告延迟问题。
缓解方式很直接:更新属性后,立刻让按钮重新获得焦点:
button.setAttribute('aria-expanded', next);
button.focus(); // 这一行很重要
- 不要加
setTimeout延迟设置aria-expanded—— 同步性一旦破坏,语义就断了 - 确保按钮是原生
<button></button>,不是<div role="button">;后者无法自然响应空格/回车,也难保证焦点管理 <li>如果按钮本身已聚焦(比如键盘操作触发),<code>.focus()不会打断体验,但能强制触发状态播报
必须搭配 aria-controls 和内容区 role
aria-expanded 单独存在毫无意义。它必须明确指向被控元素,否则屏幕阅读器不知道“展开”指的是哪块内容。
三要素缺一不可:
<button aria-expanded="false" aria-controls="panel-1">菜单</button> <div id="panel-1" role="region">...</div>
-
aria-controls的值必须严格匹配目标元素的id,大小写、连字符都不能错 - 被控容器必须设
role="region"(通用区块)或更精确的role="menu"/role="listbox",否则 AT 不认为它是独立可访问区域 - 别把
aria-expanded加在内容区上 —— 它只属于触发按钮











