原生 <details> 标签可实现无 JS 手风琴效果,<summary> 为其唯一合法首子元素;默认收起,加 open 属性可默认展开;需用 summary::marker { content: none; } 等重置样式;互斥需 JS 监听 toggle 事件并设 other.open = false;IE 不支持,现代浏览器兼容良好且语义化、可访问。

用 <details> 和 <summary> 实现基础手风琴效果
原生 HTML 的 <details> 标签天生支持展开/收起,不需要 JS 就能工作,<summary> 是它的唯一合法子元素,点击即触发切换。浏览器默认会加一个小箭头(方向随展开状态变化),这是语义化且可访问的方案。
关键点:<details> 默认是收起的;加 open 属性可默认展开:
<details open> <summary>常见问题</summary> <p>这里是详情内容,可以是任意 HTML 元素。</p> </details>
注意:<summary> 必须是 <details> 的**第一个子元素**,否则部分浏览器(如 Safari)可能不渲染箭头或无法点击。
自定义样式时必须重置 summary 的默认行为
直接写 summary { color: red; } 很可能无效——因为浏览器对 <summary> 应用了用户代理样式(user agent stylesheet),包含 list-style 和伪元素(比如 ::marker)。更麻烦的是,某些浏览器(Chrome 早期版本)把点击区域限制在文字+箭头区域,导致 padding 不响应。
实操建议:
- 用
summary::marker { content: none; }移除默认箭头,再用background或::before自定义图标 - 给
summary加display: block;,否则 inline 行为会让 padding/margin 失效 - 禁用默认 outline:
summary:focus { outline: none; },否则键盘导航时样式错乱 - 若需整个区域可点击(不只是文字),加
summary { width: 100%; }并确保父容器有宽度
多个 <details> 同时展开?默认不互斥,得靠 JS 控制
<details> 之间默认完全独立,点击一个不会关闭其他——这和多数 UI 设计师要的“手风琴”(单开)不符。要实现互斥,必须用 JS 监听 toggle 事件并手动关闭兄弟节点。
核心逻辑很简单:
document.querySelectorAll('details').forEach(detail => {
detail.addEventListener('toggle', () => {
if (detail.open) {
document.querySelectorAll('details').forEach(other => {
if (other !== detail) other.open = false;
});
}
});
});
注意两个坑:
-
toggle事件在open属性**变更后**才触发,所以判断detail.open是安全的 - 不要在
toggle里直接调用other.removeAttribute('open'),应设other.open = false,否则 Safari 可能不同步更新 DOM 状态 - 如果页面动态插入新
<details>,记得对新增节点也绑定事件
兼容性与降级:IE 完全不支持,旧版 Safari 表现异常
<details> 在 IE 中彻底不可用(显示为普通块级容器,无交互),Edge 15+、Chrome 12+、Firefox 49+、Safari 6+ 支持。但 Safari 15.4 之前有个 bug:当 <details> 嵌套在 flex 容器中时,summary 的 width: 100% 会失效。
如果项目需兼容 IE 或旧 Safari,别硬套原生标签。稳妥做法是用 <div role="region"> + aria-expanded + 手动 JS 切换 class,再配 CSS 过渡动画。而原生方案更适合现代项目——它天然支持键盘(Space/Enter 触发)、屏幕阅读器(自动 announce expanded/collapsed),这些是手写 JS 容易漏掉的。
真正容易被忽略的不是怎么写,而是是否真需要「手风琴」:很多场景下用户其实希望同时查看多个区块,强行互斥反而降低效率。先确认设计意图,再决定要不要加 JS 控制。


















