用 details 和 summary 做可折叠内容
用 <details> 包住内容,并把 <summary> 放在最前面。summary 始终可见,其余内容在读者点击之前都是隐藏的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>details</title>
</head>
<body style="font-family: system-ui, sans-serif;">
<details>
<summary>What is included in the free plan?</summary>
<p>Five projects, community support and all the beginner courses.</p>
</details>
</body>
</html>Master HTML the hands-on way
Interactive lessons, real projects, and instant feedback.
START NOW
点击问题就能展开,再点一次就会收起。浏览器会添加三角标记,让 summary 可以获得焦点,支持用 Enter 或空格键切换,并告诉屏幕阅读器它当前是展开还是收起。整个过程不涉及任何 JavaScript。
如果省略 <summary>,浏览器会显示一个默认标签(英文中是 “Details”)。
默认展开
布尔属性 open 让内容一开始就显示出来。读者切换时,浏览器会添加和移除 open,所以它也是设置样式的抓手:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>open</title>
<style>
body { font-family: system-ui, sans-serif; margin: 12px; }
details { border: 2px solid #e5e7eb; border-radius: 8px; padding: 8px 12px; margin-bottom: 8px; }
details[open] { border-color: #2563eb; }
summary { cursor: pointer; font-weight: 600; }
</style>
</head>
<body>
<details open>
<summary>Shipping</summary>
<p>Orders ship within two working days.</p>
</details>
<details>
<summary>Returns</summary>
<p>Return any item within 30 days.</p>
</details>
</body>
</html>
展开的那一项因为 details[open] 有了蓝色边框。展开 “Returns”,它也会变蓝;两项可以同时展开。
手风琴:一次只展开一项
给几个 <details> 元素设置相同的 name,它们就成为一个互斥组:打开一个,其他的就会关闭。这在以前需要 JavaScript,现在在最新的 Chrome、Edge、Firefox 和 Safari 中只靠这个属性就能实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Accordion</title>
<style>
body { font-family: system-ui, sans-serif; margin: 12px; }
.faq { max-width: 420px; border: 1px solid #e5e7eb; border-radius: 10px; }
.faq details + details { border-top: 1px solid #e5e7eb; }
.faq summary { padding: 12px 14px; cursor: pointer; font-weight: 600; }
.faq p { margin: 0; padding: 0 14px 12px; color: #374151; }
</style>
</head>
<body>
<div class="faq">
<details name="faq" open>
<summary>Is it free?</summary>
<p>Yes, the basics are free forever.</p>
</details>
<details name="faq">
<summary>Do I need to install anything?</summary>
<p>No, everything runs in the browser.</p>
</details>
<details name="faq">
<summary>Can I cancel any time?</summary>
<p>Yes, from your account settings.</p>
</details>
</div>
</body>
</html>
展开第二个问题,第一个就会收起。去掉 name 属性,它们又会各自独立展开。互斥手风琴会隐藏读者可能想对比的内容,所以在各个区域互为替代时使用它;如果读者可能想同时展开几项,就用普通的 <details>。
设置标记的样式
三角形是 summary 上的列表标记。list-style: none 会去掉它(::-webkit-details-marker 规则负责 Safari),再用伪元素画出你自己的指示符:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Custom marker</title>
<style>
body { font-family: system-ui, sans-serif; margin: 12px; }
details { max-width: 420px; background: #f9fafb; border-radius: 8px; margin-bottom: 8px; }
summary {
list-style: none; cursor: pointer;
display: flex; justify-content: space-between; align-items: center;
padding: 12px 14px; font-weight: 600;
}
summary::-webkit-details-marker { display: none; }
summary::after {
content: "+"; font-size: 20px; color: #2563eb;
transition: transform 0.2s;
}
details[open] summary::after { transform: rotate(45deg); }
summary:focus-visible { outline: 3px solid #2563eb; outline-offset: 2px; border-radius: 8px; }
details p { margin: 0; padding: 0 14px 12px; }
</style>
</head>
<body>
<details>
<summary>How long are the lessons?</summary>
<p>Most lessons take five to ten minutes.</p>
</details>
<details>
<summary>Do I get a certificate?</summary>
<p>Yes, when you finish a course.</p>
</details>
</body>
</html>
区域展开时,加号会旋转 45 度变成叉号。要像这里的 :focus-visible 轮廓一样保留可见的焦点样式:一旦去掉默认标记,键盘用户就只能靠焦点环来定位了。
toggle 事件和 open 属性
JavaScript 可以读取和设置 details.open,每次状态变化后都会触发 toggle 事件,无论是点击、按键还是脚本引起的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>toggle</title>
<style>
body { font-family: system-ui, sans-serif; margin: 12px; }
button { font: inherit; padding: 4px 10px; margin-inline-end: 6px; }
details { margin-top: 10px; border: 2px solid #e5e7eb; border-radius: 8px; padding: 8px 12px; }
#log { color: #16a34a; }
</style>
</head>
<body>
<button id="expand">Expand all</button>
<button id="collapse">Collapse all</button>
<details><summary>Step 1</summary><p>Create an account.</p></details>
<details><summary>Step 2</summary><p>Pick a course.</p></details>
<p id="log">Nothing toggled yet.</p>
<script>
const all = document.querySelectorAll('details');
all.forEach((d) => d.addEventListener('toggle', () => {
document.getElementById('log').textContent =
d.querySelector('summary').textContent + (d.open ? ' opened' : ' closed');
}));
document.getElementById('expand').onclick = () => all.forEach((d) => { d.open = true; });
document.getElementById('collapse').onclick = () => all.forEach((d) => { d.open = false; });
</script>
</body>
</html>
“Expand all” 会给两项都设置 open,日志显示最后一次 toggle 事件。这个事件不会冒泡,所以要在每个 <details> 元素上监听,而不是在父元素上监听。
summary 里能放什么
summary 里可以放文字、行内元素;如果这个区域的标题属于页面大纲,也可以放一个标题:
<details>
<summary><h3>Refund policy</h3></summary>
<p>...</p>
</details>
不要在 <summary> 中放链接、按钮和表单字段:summary 本身已经是一个控件,里面再放控件很容易被忽略或误点,屏幕阅读器对嵌套控件的朗读效果也很差。把交互内容放在 <details> 的正文中。
常见错误
用 <div> 和 JavaScript 自己做切换。 <details> 已经处理好了点击、按键和屏幕阅读器状态。确实需要自定义切换时,请用带 aria-expanded 的按钮 。
没把 <summary> 放在最前面。 它必须是 <details> 的第一个子元素。
隐藏了标记却没有替代品。 给读者一些提示,让他们知道 summary 可以展开,比如上面的加号。
用 display 隐藏内容。 请切换 open;强制显示或隐藏内容的 CSS 会和浏览器的状态冲突。
对需要比较的内容使用互斥组。 只有在一次展开一个区域合理时才使用 name。