是最轻量、语义化且无需 js 即可实现表单辅助说明收起/展开的原生方案,天然支持键盘导航、屏幕阅读器及焦点管理,避免手写 toggle 带来的可访问性与 ssr 问题。

<details></details> 是最轻量、最语义化的方式,能原生实现长表单辅助说明的收起/展开,无需 JS 即可工作,且天然支持键盘导航和屏幕阅读器。
为什么不用 JavaScript 手写 toggle?
手写 JS 控制显隐,容易忽略焦点管理、aria-expanded 同步、键盘响应(如空格/回车触发)、以及 SSR 渲染时的 hydration 闪动。而 <details></details> 自带这些能力:用户按 Enter 或 Space 也能触发,open 属性变化自动更新可访问性状态,服务端直出 open 后客户端不会二次重绘。
常见错误现象包括:用 div + click 事件模拟后,视障用户无法感知内容是否已展开;或 JS 加载慢导致页面先闪出全部说明再收起。
如何让 <summary></summary> 点击区域更大、更易触达?
移动端默认只有文字区域响应点击,极易误触失败。必须手动扩大热区:
- 给
<summary></summary>设置display: block和足够padding(至少12px上下左右) - 移除可能存在的
user-select: none(某些 CSS 重置库会加,会导致长按复制而非展开) - 避免在
<summary></summary>内嵌套button或a标签——它们会劫持点击行为,破坏原生逻辑
示例样式片段:
details summary {
display: block;
padding: 12px 16px;
cursor: pointer;
}
怎么让多个表单说明互斥(“手风琴”效果)?
<details></details> 原生不支持自动关闭其他兄弟节点。若需“展开一个、收起其余”,必须手动监听 toggle 事件并遍历控制:
- 不能监听
click—— 用户用键盘展开时不会触发 - 必须监听
toggle,且只在el.open === true时才执行收起其他逻辑(避免重复操作) - 注意:设置
otherEl.open = false不会触发它们的toggle事件,所以不会形成无限循环
简短实操代码:
document.querySelectorAll('details.form-help').forEach(el => {
el.addEventListener('toggle', () => {
if (el.open) {
document.querySelectorAll('details.form-help').forEach(other => {
if (other !== el) other.open = false;
});
}
});
});
兼容性与 SSR 注意事项
Chrome 12+、Firefox 49+、Safari 12.1+、Edge 79+ 均完整支持;IE 完全不支持,需降级为始终显示(用 @supports 检测)。
SSR 场景下最容易被忽略的一点:如果服务端渲染了 <details open></details>,但客户端 JS 还没加载完,用户可能看到“已展开 → 突然收起 → 再展开”的三段式闪动。解决方法只有两个:
- 服务端不设
open,统一由用户首次交互触发(最稳妥) - 若必须默认展开,请确保 hydration 阶段极快,或用
data-hydrated类配合 CSS 隐藏未激活态
真正复杂的不是写法,而是判断哪些说明值得折叠——比如必填字段的校验规则建议保留常显,而“高级正则语法示例”才适合放进 <details></details>。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











