多级折叠展开必须用 ul/li + checkbox + CSS :checked ~ .submenu 实现,因 iOS Safari 和微信 X5 内核不支持嵌套 details,会导致点击失效、状态不同步;需 UA 检测降级为 div+button 结构并手动补全 ARIA 属性。

多级折叠展开不能靠嵌套 details 实现“真正可靠”的树状目录——iOS Safari 和微信 X5 内核下嵌套会直接失效,状态不同步是必然结果;必须用 ul/li 嵌套结构 + input[type="checkbox"] + CSS :checked ~ .submenu 组合,才能兼顾可访问性、状态记忆和跨端一致性。
为什么 details 嵌套在微信和 iOS 上会失效
微信 X5 内核和旧版 iOS Safari 对 details 的嵌套支持极差:子 details 不响应点击、aria-expanded 不同步、键盘 Enter/Space 无法触发。这不是“偶尔出错”,而是内核层面不识别嵌套 DOM 关系。检测到 Micromessenger UA 时,应直接降级为 div+button 结构,并手动补全 aria-expanded 和 tabindex="0"。
- 错误写法:
<details><summary>一级</summary><details><summary>二级</summary></details></details>(在微信中二级点不动) - 正确兜底逻辑:UA 匹配
/MicroMessenger/i时,不渲染任何details,统一用<div role="menuitem"> <button aria-expanded="false">一级</button><ul class="submenu">...</ul> </div> - 别依赖 polyfill——X5 内核不执行 JS,polyfill 无效
ul/li 嵌套结构的 DOM 层级硬约束
浏览器和屏幕阅读器只通过 DOM 树层级识别父子关系。子 ul 必须是父 li 的**直接子元素**,中间不能插入 div、换行符或空格文本节点,否则 aria-expanded 同步失败、键盘导航跳过子菜单。
- ✅ 正确:
<li>产品<ul><li>笔记本</li></ul> </li> - ❌ 错误:
<li>产品</li> <ul><li>笔记本</li></ul>(子ul在父li外) - ❌ 错误:
<li>产品<div><ul><li>笔记本</li></ul></div> </li>(div插入破坏直接包含关系) - 所有可展开项必须带
aria-haspopup="true"和初始aria-expanded="false"
input[type="checkbox"] + label + ~ .submenu 的 CSS 触发条件
这个组合是目前唯一无需 JS 就能记住展开状态、兼容触摸设备、且保持语义完整的纯 CSS 方案。但选择器对 HTML 结构极其敏感:中间插一个空格、换行或注释都会让 ~ 失效。
立即学习“前端免费学习笔记(深入)”;
-
<input type="checkbox" id="menu-1">必须紧邻<label for="menu-1">菜单项</label>,两者之间不能有任何标签、文本或空白符 -
<ul class="submenu"></ul>必须紧跟在label后面,且是它的**直接兄弟节点**(不是后代) - CSS 规则必须写成:
input[type="checkbox"]:checked ~ .submenu { display: block; } - 禁用
visibility: hidden或opacity: 0——它们仍占布局空间或焦点可达,违反可访问性
移动端强制收起子菜单的必要性
小屏下若允许多级同时展开,内容区会被严重挤压,甚至触发横向滚动。这不是体验问题,而是布局失控。必须用 @media (max-width: 768px) 强制重置所有子菜单为 display: none,且不依赖 JS 切换。
- 错误做法:只靠 JS 监听 resize 后 toggle class,页面加载时状态未初始化,首次打开就错乱
- 正确做法:
@media (max-width: 768px) { .submenu { display: none !important; } } - 缩进统一用
padding-left+border-left,不用margin-left——避免 tab 键聚焦框偏移 - 所有层级共用
.submenu类名,靠父级深度用 CSS 选择器区分样式,例如:li > ul.submenu { padding-left: 2.5rem; }
最易被忽略的一点:微信 X5 内核不执行 JS,也不识别嵌套 details,所以“先写 JS 降级逻辑”没用——必须从 HTML 结构层就做 UA 判断并输出不同 DOM。一旦结构错了,后续所有 CSS 和 ARIA 都白搭。



















