原生 details/summary 可访问折叠面板实战
【摘要】 原生 details/summary 已提供展开状态、焦点与 Enter/Space 切换,本文用最小 HTML、CSS 和可选互斥脚本演示折叠面板,并说明 summary 内不要放按钮的原因、边界与读屏验证步骤。
原生 details/summary 可访问折叠面板实战
问题场景
FAQ、设置项或说明区常需要折叠面板。用 div 加点击事件实现时,开发者还要自己维护 aria-expanded、tabindex、焦点样式以及 Enter/Space 键行为,容易遗漏。原生 details 与 summary 已经把“展开/折叠”交互交给浏览器:summary 可获得焦点,按 Enter 或空格切换,辅助技术也能读取展开状态。
本文给出一份可直接运行的最小示例,并重点说明一个常见误区:不要在 summary 内再放 button、a 或 input。
为什么 summary 内不要再放按钮
反例通常写成这样:
<details>
<summary>
<span>面板标题</span>
<button type='button'>复制</button>
</summary>
<p>面板内容。</p>
</details>
问题不在于“按钮不能点击”,而在于 summary 本身已经是负责展开/折叠的交互控件。把按钮嵌进去后会出现三类歧义:
- 点击按钮时,事件可能冒泡到
summary,导致用户想执行按钮动作,却意外触发面板折叠。 - 键盘焦点落在内部按钮上时,Enter/Space 应激活按钮,而不是切换
summary,用户和读屏软件都难以判断当前操作对象。 - 无障碍树中会出现嵌套的交互控件,摘要的“可展开”语义被削弱,读屏播报也更冗长。
如果确实需要“复制”“删除”等操作,应把按钮放到 summary 外;若操作只对展开后的内容生效,可放进 details 的内容区,但要接受折叠时该操作不可见。
最小可用示例
将下面内容保存为 index.html,无需构建工具。示例使用占位标题和内容,可按实际业务替换。
<!doctype html>
<html lang='zh-CN'>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<title>原生折叠面板示例</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
color: #1f2328;
background: #f6f8fa;
}
.wrap {
max-width: 720px;
margin: 40px auto;
padding: 0 16px;
}
.panel {
margin: 12px 0;
background: #fff;
border: 1px solid #d0d7de;
border-radius: 8px;
}
.panel summary {
padding: 14px 16px;
font-weight: 600;
cursor: pointer;
}
.panel summary:focus-visible {
outline: 3px solid #005fcc;
outline-offset: 2px;
}
.panel[open] summary {
border-bottom: 1px solid #e5e7eb;
}
.panel summary::marker {
color: #005fcc;
}
.panel .content {
padding: 12px 16px;
line-height: 1.7;
}
</style>
</head>
<body>
<main class='wrap'>
<h1>常见问题</h1>
<details class='panel'>
<summary>如何展开这个面板?</summary>
<div class='content'>
<p>用 Tab 将焦点移到摘要上,按 Enter 或空格切换。</p>
</div>
</details>
<details class='panel'>
<summary>折叠后内容还能被键盘访问吗?</summary>
<div class='content'>
<p>默认关闭时内容不显示,键盘和读屏浏览通常也不会进入其中。</p>
</div>
</details>
<details class='panel'>
<summary>如何设置默认展开?</summary>
<div class='content'>
<p>在 details 上添加 open 属性即可,例如 details class='panel' open。</p>
</div>
</details>
</main>
</body>
</html>
以上三个面板彼此独立。若需要“每次只展开一个”,可以增加可选脚本,不要拦截 summary 的点击或键盘事件:
<script>
const panels = Array.from(document.querySelectorAll('details.panel'));
panels.forEach((panel) => {
panel.addEventListener('toggle', () => {
if (!panel.open) return;
panels.forEach((other) => {
if (other !== panel) other.open = false;
});
});
});
</script>
这段脚本只监听原生 toggle 事件,展开一个面板时关闭其他面板,不改变 Enter/Space 的默认行为。
运行与验证方法
保存文件后用浏览器打开,按以下顺序检查:
- 按 Tab 键,确认焦点能移到每个
summary,并且焦点轮廓清晰可见。 - 在
summary上按 Enter 和空格,确认面板展开、折叠状态正确切换。 - 用鼠标点击摘要文字,确认只切换一次,不会因事件冒泡重复切换。
- 在浏览器开发者工具的无障碍面板中查看
summary,确认其可访问名称来自摘要文字,展开状态随操作变化。 - 用读屏软件按 Tab 聚焦摘要,确认能听到标题及折叠或展开状态;激活后检查内容可读,折叠后内容不再进入浏览顺序。不同浏览器与读屏组合的播报措辞可能不同,应以目标环境实测为准。
边界与限制
details的内容应放在summary之后;不要把summary当成普通容器塞入多个交互控件。- 不要为了美观直接删除焦点轮廓;可以自定义
:focus-visible样式,但要保持明显可见。 - 默认的展开/折叠是即时完成的。若要做高度动画,需要额外包装和脚本,并重新验证关闭状态下的焦点与读屏行为。
- 自定义
summary标记时,保留可点击区域和状态提示,避免只靠颜色表达展开状态。 - 互斥脚本属于渐进增强;禁用脚本时,多个面板仍可独立使用。
- 若业务要求复杂菜单、嵌套弹层或摘要内操作按钮,应重新评估组件结构,而不是把交互压力都放进
summary。
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)