原生 标签可零 js 实现语义化 faq 折叠,需严格遵循结构规范( 必为首个直接子元素且仅含内联元素),多选一需 js 监听 toggle 事件,动画靠 max-height 过渡,兼容性差时应降级为 aria 手风琴。

用 <details></details> 和 <summary></summary> 快速搭出 FAQ 手风琴
现代浏览器(Chrome 12+、Firefox 49+、Safari 6.1+、Edge 79+)原生支持 <details></details>,不用写 JS 就能实现语义化折叠。它自带键盘操作(空格/回车触发)、屏幕阅读器识别(自动加 aria-expanded),且默认收起——加 open 属性即可默认展开单个项。
关键结构必须严格:
- <summary></summary> 必须是 <details></details> 的**直接且首个子元素**
- <summary></summary> 内只能放内联元素(<strong></strong>、<span></span> 可以,<p></p>、<div> 不行)
- 中间插任何包裹标签(比如 <code><div><summary>...</summary></div>)会导致点击无响应,且不报错
基础写法示例:
<details><summary>为什么我的订单还没发货?</summary><p>我们通常在付款后 24 小时内处理订单。如遇节假日,可能延迟 1–2 天。</p> </details>
让多个 FAQ 项互斥展开(单开模式)必须用 JS 监听 toggle 事件
<details></details> 默认各自独立,点一个不会关掉其他——要“手风琴”效果就得加 JS。但别用 click,它无法捕获键盘操作(空格/回车),而且状态变化后才派发 toggle,更可靠。
实操要点:
- 给所有 <details></details> 加统一 class(如 js-faq-item),方便批量控制
- 只在 e.target.open === true 时执行关闭逻辑,避免收起时误触发
- 动态插入的新项需重新绑定,或改用事件委托(监听父容器)
简短可用代码:
document.querySelectorAll('.js-faq-item').forEach(item => {
item.addEventListener('toggle', e => {
if (!e.target.open) return;
document.querySelectorAll('.js-faq-item').forEach(other => {
if (other !== e.target) other.open = false;
});
});
});
加平滑展开动画不能靠 height,得用 max-height + transition
<details></details> 本身不支持 CSS 过渡,height: auto 也无法 transition。唯一可行路径是用 max-height 模拟——但这个值必须足够大,又不能过大导致收起延迟。
常见错误与对策:
- 硬写死 max-height: 500px:内容超长会被截断;内容很短则收起动画拖沓
- 正确做法是设一个保守上限(比如 max-height: 600px),并确保所有 FAQ 内容高度都不超过它
- 若内容高度差异极大,必须用 JS 测量 scrollHeight 后动态赋值,否则动画会失真
- 动画作用对象应是 details[open] summary ~ *,不是 <details></details> 本身,否则影响布局流
CSS 示例片段:
details summary ~ * {
overflow: hidden;
max-height: 0;
transition: max-height 0.3s ease-out, opacity 0.3s ease-out;
}
details[open] summary ~ * {
max-height: 600px;
opacity: 1;
}
样式重置和兼容性兜底不能只靠 CSS
想换掉默认三角图标?summary::marker { content: "" } 在 Chrome/Firefox 有效,但 Safari 需额外加 list-style: none。用伪元素加自定义箭头时,注意 details[open] summary::after 的旋转方向是否匹配。
IE 完全不支持 <details></details>,若项目必须兼容,就别用它——降级方案不是加 polyfill,而是直接切换为 <button></button> + <div> + <code>aria-expanded + JS 控制的完整 ARIA 手风琴。此时必须:
- button 上同步设置 aria-expanded 和 aria-controls
- 对应内容区设 id 并匹配 aria-controls 值
- 收起时设 aria-hidden="true",展开时设 "false"
- 切勿仅靠 CSS 类名(如 .is-open)判断状态,辅助技术看不到
真正容易被忽略的点是:即使用了原生 <details></details>,如果页面里混用了 JS 控制的其他折叠组件,焦点管理、键盘循环、tabindex 顺序这些细节一旦没对齐,整个 FAQ 区域的可访问性就崩了。











