details和summary必须严格满足结构要求才能正常工作:summary必须是details的第一个子元素,中间不能有空格、换行或注释;open属性仅通过HTML存在与否生效,JS赋值无效;状态监听须用toggle事件而非click。

details 和 summary 能直接实现无 JS 折叠,但写错一个空格、放错一个位置,就完全不工作——不是浏览器不支持,而是结构被破坏了。
summary 必须是 details 的第一个子元素,且中间不能有空格或注释
这是最常踩的坑。哪怕 <details>\n <summary>标题</summary> 这种换行,在 Safari 中也会让 summary 失效:它变成普通文本,点击无反应,小三角图标消失,控制台还不报错。
常见错误现象:
- 点击后内容始终可见(相当于默认
open但无法关闭) -
summary渲染成纯文字,没有折叠控件语义 - 用
display: block强制样式后,::marker图标被干掉
正确写法只有一种:<details><summary>标题</summary>内容…</details>,<summary> 紧贴 <details> 开始标签,中间零字符(包括空格、换行、HTML 注释)。
立即学习“前端免费学习笔记(深入)”;
open 属性只能通过 HTML 属性或 toggleAttribute 控制
想默认展开?必须写 <details open><summary>…</summary></details>。JS 写 el.open = true 或 el.setAttribute('open', 'true') 都无效——浏览器只认初始 HTML 是否存在该布尔属性。
切换状态时也一样:
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
- ✅ 正确:
el.toggleAttribute('open') - ❌ 无效:
el.open = false(UI 不变) - ❌ 错误:
el.setAttribute('open', 'false')(会变成字符串值,仍视为 true) - ✅ 关闭:
el.removeAttribute('open')
SSR 场景下尤其要注意:服务端渲染时动态输出 open 属性,比客户端 JS 补开更稳,避免水合后内容先闪一下再收起。
监听状态变化要用 toggle 事件,不是 click
details 的状态变更发生在浏览器内部逻辑完成之后,click 事件触发太早,此时 details.open 还没更新。
所以手风琴效果(点一个开,其他关)必须这样写:
document.querySelectorAll('details').forEach(details => {
details.addEventListener('toggle', () => {
if (details.open) {
document.querySelectorAll('details').forEach(d => {
if (d !== details) d.removeAttribute('open');
});
}
});
});
注意点:
- 不要给
summary绑click,会干扰原生行为 -
toggle事件在展开/收起都触发,靠details.open判断方向 - 多个
details共存时,别漏掉if (d !== details)判断,否则自己把自己关掉
无障碍问题:summary 文本会被读屏器读两次
这不是 bug,是规范行为:summary 既是控件(朗读一次),又是内容第一行(展开后再次朗读)。对视障用户来说,相当于“帮助中心”点了以后,又听到一遍“帮助中心”。
缓解方式有限,但可选:
- summary 里只放关键词,避免完整句子(如用“FAQ”而非“常见问题解答”)
- 展开后的内容首句避开与 summary 完全重复
- 不用
aria-label覆盖,那会破坏原生语义;真要定制,优先用aria-labelledby指向外部唯一 ID
真正难处理的不是怎么写出来,而是怎么让 summary 在 DOM 树里“干净地站第一位”,以及怎么让 JS 切换和无障碍体验不互相打架——这两点,比加动画或换图标重要得多。


















