网页里的手风琴组件随处可见——常见问题解答、商品参数、设置面板,点一个展开,其他的自动收起来。要实现这个效果,以往要么自己写一堆JavaScript监听点击、遍历其他面板、手动移除open属性,要么从某个UI库里引一个组件。代码虽然不算复杂,但几个面板的展开收起逻辑,配合动画再加点无障碍处理,几十行JS是跑不掉的。如果项目里恰好不用框架或者需要极致的轻量,这点小事摊上这么多脚本总有些不值得。
好消息是浏览器已经把这件事接管了。<details>元素从诞生起就负责折叠面板,但默认行为是各自独立的——开一个不会关另一个。从Chrome 120、Edge 120、Safari 17.2、Firefox 125开始,details新增了一个name属性,只要给同一组的details设置相同的name值,它们就会自动变成互斥手风琴:打开一个,同名的其他面板全部收起来,不需要一行JavaScript。这篇文章就用一个常见的FAQ页面来演示这个新特性怎么用,以及和传统写法相比到底省了多少事。
传统手风琴的典型写法
先回顾一下我们以前怎么做的。假设有一个FAQ区域,每个问题用details包裹,要保证任何时候只有一个处于展开状态,需要监听每个details的toggle事件,然后手动循环关上不属于当前触发的那些。
<div class="faq-list">
<details>
<summary>支付方式有哪些?</summary>
<p>支持微信、支付宝、银行卡。</p>
</details>
<details>
<summary>多久能发货?</summary>
<p>下单后24小时内。</p>
</details>
<details>
<summary>可以退货吗?</summary>
<p>七天无理由退货。</p>
</details>
</div>
<script>
const allDetails = document.querySelectorAll('.faq-list details');
allDetails.forEach(detail => {
detail.addEventListener('toggle', () => {
if (detail.open) {
allDetails.forEach(d => {
if (d !== detail) d.open = false;
});
}
});
});
</script>
这段代码功能上没问题,但有一些不足之处:如果details是动态加载的(比如通过Ajax追加),就得重新获取节点列表或使用事件委托,逻辑又会膨胀一圈。再加上有时候需要保留当前展开项不被用户再次点击收折(即始终有一项展开),那就还要额外判断如果点击的是已经展开的项就阻止默认行为,越写越乱。这些工作几乎每个项目都会遇到,但又都是重复的。
name 属性带来的变化
上面那段JS,现在可以用一个name属性完全替代。给同一组details都加上相同的name值(类似单选按钮的name),浏览器就会自动管理互斥状态。
<div class="faq-list">
<details name="faq">
<summary>支付方式有哪些?</summary>
<p>支持微信、支付宝、银行卡。</p>
</details>
<details name="faq">
<summary>多久能发货?</summary>
<p>下单后24小时内。</p>
</details>
<details name="faq">
<summary>可以退货吗?</summary>
<p>七天无理由退货。</p>
</details>
</div>
对,就这样。页面加载后点击任意一个summary,它会展开,同时同一name下的其他details会自动关闭。没有任何标签。如果用户再次点击已展开的标题,它就会收起来,这一点和之前的行为一致——浏览器没有强制要求“始终必须有一个展开项”,这种灵活性反而更符合大多数场景的预期。
如果某个面板想独立于这个互斥组,不给它加name属性或者用一个不同的name就行。一组手风琴里可以混搭:大部分带name形成互斥组,个别不带name的可以独立开合,不影响其他人。
完整案例:FAQ手风琴页面
现在把这个FAQ做得完整一些,加上几个分类,每组内用手风琴,组之间互不影响。结构清晰,HTML自己就能说明意图。
<section>
<h2>常见问题</h2>
<h3>支付相关</h3>
<details name="payment">
<summary>支持哪些支付方式?</summary>
<p>微信支付、支付宝、银联卡。</p>
</details>
<details name="payment">
<summary>支付安全吗?</summary>
<p>所有交易均通过PCI DSS认证,数据加密传输。</p>
</details>
<h3>物流相关</h3>
<details name="shipping">
<summary>多久能发货?</summary>
<p>每日16点前的订单当日发货。</p>
</details>
<details name="shipping">
<summary>如何查询物流?</summary>
<p>在「我的订单」页面输入运单号即可查看。</p>
</details>
<details name="shipping">
<summary>是否支持自提?</summary>
<p>暂不支持,请选择快递配送。</p>
</details>
<h3>售后相关</h3>
<details name="aftersale">
<summary>退货政策是怎样的?</summary>
<p>签收后7天内无理由退货。</p>
</details>
<details name="aftersale">
<summary>退款多久到账?</summary>
<p>退货签收后3个工作日内原路退还。</p>
</details>
</section>
完全不需要JavaScript,三个组之间独立,组内互斥。如果需要让某个分类的首个项目默认展开,直接在对应的details上写open属性就行:<details name="payment" open>。有name属性的details照样支持open属性初始化展开,不会因为有了互斥组就不让预置展开项。
加上一点可选的用户体验优化
有时我们希望当用户点击当前已展开的标题时,不要把它收起来,而是保持展开(这样始终有一个面板打开,避免所有都收折的空白状态)。这个需求可以用一小段JS补充实现——但核心的手风琴逻辑仍旧靠name属性驱动,不需要我们接管切换逻辑。
document.querySelectorAll('details[name]').forEach(details => {
details.addEventListener('toggle', () => {
if (!details.open) {
// 如果尝试关闭当前展开项,强制重新打开
details.open = true;
}
});
});
这段代码做的事只是捕获关闭动作并阻止它,确保始终有一项打开。和传统的“完全手动管理open状态”从复杂度和代码量上完全不是一个级别。
与锚点定位的配合
details的手风琴模式还天然支持URL锚点。当URL hash指向某个details内的元素时,浏览器会展开那个details并滚动到对应位置。结合name属性,它也会关闭同组其他项,体验很流畅。如果你的FAQ页面需要支持直接链接到某个问题(如/faq#shipping-schedule),这种效果用纯HTML就能达到,不需要拦截hash变化自己写展开逻辑。
兼容性评估与降级
截至2025年初,name属性在Chrome、Edge、Safari、Firefox的最近几个大版本中均已支持。全球覆盖率超过90%。如果项目需要兼容一些更老的浏览器,不支持的浏览器会忽略name属性,details仍然可以独立开合,只是失去了互斥行为——相当于退化为普通的折叠面板,功能还在,不影响信息展示。
如果你想在不支持的浏览器上补上互斥逻辑,可以先检测是否支持name属性,不支持时再加载传统的JS实现。一个简单的特性检测:
if (!('name' in HTMLDetailsElement.prototype)) {
// 加载传统JS手风琴脚本
import('./accordion-polyfill.js');
}
至于样式调整,details与summary的默认样式在各浏览器里略有差异,可以通过CSS统一。比如去掉默认的小三角换上自定义图标:
summary::-webkit-details-marker { display: none; }
summary::marker { content: ''; }
summary::before {
content: '▶';
display: inline-block;
margin-right: 8px;
transition: transform 0.2s;
}
details[open] summary::before {
transform: rotate(90deg);
}
这部分完全不需要JavaScript参与。
小结
一项新属性替换掉几十行JavaScript,这种进化感觉像是浏览器在慢慢把前端工程师从琐碎的交互逻辑里解放出来。手风琴是一个足够通用、足够高频的UI模式,name属性的加入让details元素从“折叠面板”升级成了“真正的原生手风琴”。零依赖、原生无障碍支持、自动处理键盘操作,代码反而比以前更少。
如果你正在开发一个重内容的网站——帮助中心、产品介绍、政策说明页——不妨把那些还在依赖手写JS的手风琴换成带name的details,改造成本极低,可维护性却能提升一截。

