details+summary单层折叠最省事,原生支持键盘操作、屏幕阅读器识别且无需JS;但必须确保summary为details首个子元素,iOS微信中多级菜单需改用checkbox+label+ul结构并严格遵循DOM顺序。

用 details + summary 做单层折叠最省事
只要内容不需要多级嵌套、不强求 iOS 微信内稳定运行,details 就是首选。它原生支持键盘空格/回车触发、屏幕阅读器自动识别、无需 JS 就能记住展开状态。
常见错误现象:<details><p>内容</p></details> 点击没反应——因为缺了 summary;或者把 summary 写成第二个子元素,浏览器直接忽略。
-
summary必须是details的第一个子元素,中间不能有空格、换行或其它标签 - 默认关闭,加
open属性可初始展开:<details open><summary>标题</summary>内容</details> - 隐藏原生箭头用
summary::marker { content: "" }(Safari 还要加list-style: none) - 动画只能靠
max-height+transition模拟,不能设height: auto—— CSS 过渡不支持这个值
做多级菜单必须用 ul/li + input[type="checkbox"]
嵌套 details 在 iOS Safari 和微信 X5 内核下必然失效:子菜单点不开、状态不同步、焦点跳失。这不是兼容性补丁能解决的,是底层解析逻辑不支持。
正确结构只有一种:input → 紧跟 label → 紧跟 ul.submenu,三者必须是连续兄弟节点,中间不能插任何东西(包括 HTML 注释和空白符)。
立即学习“前端免费学习笔记(深入)”;
- CSS 规则必须写成:
input[type="checkbox"]:checked ~ .submenu { display: block; } - 不能用
visibility: hidden或opacity: 0,否则键盘 Tab 仍会聚焦到不可见内容,违反可访问性 - 移动端(
@media (max-width: 768px))必须重置所有.submenu为display: none,不能靠 JS 切换——小屏下同时展开多级会挤爆视口 - 防穿透关键:用
:has(> .submenu)或降级 class(如.has-child)限定样式作用域,避免.active ul一次展开所有后代
JS 只该管“状态同步”,别碰 style.display
用 JS 直接写 el.style.display = "none" 是自找麻烦:它会覆盖 CSS 文件里的媒体查询、伪类规则,后续调试时完全找不到来源。
真正该做的,是把状态存在语义化位置,让 CSS 自己响应:
- 优先用自定义属性:
el.setAttribute("data-open", "true"),再写 CSS:[data-open="true"] .submenu { max-height: 300px; } - 互斥逻辑(手风琴效果)必须监听
toggle事件(不是click),因为键盘操作也会触发toggle - 事件委托绑定父容器,而不是每个菜单项单独绑:
menuContainer.addEventListener("click", e => { const item = e.target.closest(".menu-item"); if (item) toggleItem(item); }) - iOS 上记得在
touchstart阶段调e.preventDefault(),否则 click 会触发两次
容易被忽略的可访问性硬要求
很多菜单看着能点、能展开,但盲人用户根本没法用——问题常出在 DOM 结构和 ARIA 属性漏写。
- 顶级
ul必须有role="menubar",每层子ul加role="menu",每个可点击项(a或button)加role="menuitem" - 所有可展开项必须带
aria-haspopup="true"和初始aria-expanded="false",否则屏幕阅读器不会提示“这个可以展开” - 缩进统一用
padding-left,别用margin-left——后者会让键盘聚焦框错位到文字左边空白处 - 微信 X5 内核检测到
MicromessengerUA 时,应直接降级为div+button结构,并补全a标签,不能指望details工作



















