原生标签自带无障碍支持,无需添加aria属性;屏幕阅读器自动播报“已折叠/展开”状态;手动添加aria-expanded会引发冲突;必须保留原生键盘操作与焦点逻辑。

details标签在屏幕阅读器中会自动播报“已折叠”或“已展开”状态
原生 <details></details> 标签不需要任何额外 ARIA 属性,浏览器内部已为它绑定 role="group"、键盘支持(空格/回车切换)和状态播报逻辑。屏幕阅读器(如 NVDA、VoiceOver、JAWS)聚焦到 <summary></summary> 时,会自然读出类似“帮助信息,已折叠”或“帮助信息,已展开”的完整语义,而不是只读文字内容。
常见错误现象:
- 用户听到“帮助信息”但没听到状态 → 检查是否漏写
<summary></summary>,或写了但内容为空(浏览器可能生成默认“详细信息”文本,但语义弱、播报不一致) - 反复点击后状态播报滞后或错乱 → 多数因 JS 动态替换
<details></details>内容后未触发 DOM 重排,或在toggle事件回调中同步修改了 DOM 但未用queueMicrotask延迟读取open值
不要手动加 aria-expanded 到原生 details 上
显式给 <details></details> 或 <summary></summary> 添加 aria-expanded 是冗余且危险的:浏览器可能忽略该属性,也可能与内部状态冲突,导致屏幕阅读器播报错误(比如实际已展开却读“已折叠”)。
只有在以下情况才需手动设置:
- 用
<div> + JS 模拟 <code><details></details>行为(即放弃原生语义) - 组件库封装时需向外暴露状态供测试或外部逻辑读取(如 React 组件 props)
- 极少数旧版读屏器(如 JAWS 2019 之前版本)对原生
<details></details>支持不稳定,且项目明确要求兼容它们 -
summary { pointer-events: none; }→ 点击失效,键盘操作也失效 -
summary { display: block; }→ 可能干扰::marker渲染,导致 Firefox 下箭头消失且状态播报异常 - 给
<summary></summary>包裹<button></button>或添加onclick→ 触发双重事件,或阻止原生 toggle 行为 - 用
tabindex="-1"移除焦点 → 屏幕阅读器用户无法通过键盘访问 - 多次展开/收起后重复 fetch 和渲染 → 应加标记(如
data-loaded="true")或用if (!details.hasAttribute('data-loaded'))控制 - 新插入的内容含表单控件,但焦点未落到首个可交互元素 → 用户展开后仍需多按几次 Tab 才能操作
- 插入后
<summary></summary>失去焦点,影响连续操作流 → 可在插入后手动summary.focus(),但需确认浏览器允许(部分 Safari 版本有策略限制)
若真要加,必须严格同步:JS 修改 element.open = true 后,立刻更新对应元素的 aria-expanded 值,并确保只加在触发焦点的元素上(通常是 <summary></summary>)。
键盘操作和焦点行为必须保留原生逻辑
<summary></summary> 默认可被 Tab 键聚焦,按空格或回车即可切换状态——这个流程不能被 CSS 或 JS 破坏。
容易踩的坑:
动态加载内容时,toggle 事件里插入 HTML 要防重复和焦点丢失
很多场景是首次展开才请求接口并插入详情内容。这时若直接在 toggle 回调里 innerHTML = response,容易出现:
真正关键的一点是:别把 <details></details> 当成纯视觉组件来 hack。它的价值不在“能不能动”,而在于“动的时候,所有用户——无论用眼、用耳、用键盘还是用触控——都获得一致、可靠、无需解释的反馈”。一旦开始绕过原生行为,就等于主动放弃了这份保障。











