原生<details>不适用于多级树状标签页,因其在Safari中嵌套时open状态易重置、无法初始展开指定层级、<summary>点击热区与图标不可控、缺乏键盘空格/回车支持、无“点文字vs点箭头”事件区分,且内部表单控件易误触;真实树状导航需用<ul>嵌套+aria-expanded+max-height过渡+事件委托精准控制。

原生 HTML 没有“树状结构标签页”这个概念,所谓逐级展开切换,本质是用 <ul> 嵌套 + aria-expanded 状态 + max-height 过渡实现的可交互层级导航,不是靠某个标签自动完成。
为什么不能直接用 <details> 做多级树状标签页
<details> 在 Safari 中嵌套时 open 状态会意外重置;无法初始展开指定层级(服务端渲染后全闭合);<summary> 的点击热区和图标样式不可控;不支持键盘空格/回车切换;没有事件区分“点文字”和“点箭头”,内部 <input type="checkbox"> 一碰就触发展开。这些在真实树状标签页中全是硬伤。
必须用 <ul> 结构 + 事件委托精准定位子 <ul>
每个可展开节点要带 data-expandable="true",子 <ul> 必须紧跟在触发按钮之后(不能隔元素),否则 button.nextElementSibling 找不到目标。别用 querySelector('ul') —— 它可能匹配到兄弟节点里的 <ul>,导致误操作。
- 监听外层容器(如
<ul class="tree">)的 click 事件,用event.target.closest('li[data-expandable]')捕获目标节点 - 立刻调用
event.stopPropagation(),防止点击内部复选框或链接时触发父节点展开 - 取
button.getAttribute('aria-expanded') === 'true'判断状态,别用=== 'true'字符串比较布尔值 - 更新
aria-expanded和max-height同步进行,避免状态与视觉不同步
max-height 过渡怎么设才不翻车
不能只靠 hidden 属性——切换太快,用户感知不到层级关系;也不能用 display: none,它会让子节点不可访问,破坏无障碍。正确做法是:
立即学习“前端免费学习笔记(深入)”;
- 默认设
max-height: 0; overflow: hidden; - 展开时设为足够大的固定值,比如
max-height: 500px;(别用max-height: max-content,多数浏览器不支持动画) - 加
transition: max-height 0.2s ease-in-out;,别忘了overflow: hidden必须始终存在 - 缩进统一用
padding-left,别依赖li:nth-of-type(1) > ul这类选择器——动态增删节点时样式会错乱
键盘操作和无障碍怎么补上
没 aria-expanded 和 aria-hidden 就不算可用的树状标签页。屏幕阅读器靠它判断状态,键盘用户靠 Tab 进入、Space/Enter 切换。图标必须用 aria-hidden="true" 隐藏,避免重复朗读。
- 每个可展开按钮加
role="button"和tabindex="0" - 展开后,子菜单第一项应自动获得焦点(用
ul.querySelector('a, button').focus()) - 用
keydown监听Space和Enter,行为与 click 一致 - 视觉指示符(▶️ / ▼)必须随
aria-expanded动态切换,不能只靠 CSS 类名推断
真正难的不是写第一次展开,而是状态同步:DOM、aria-expanded、max-height、焦点、键盘响应、动态插入新节点后的查找逻辑——这五者只要一个脱节,用户就会卡在某一级出不来。



















