Bootstrap 5 原生不支持手机端多级下拉菜单,因 Dropdown 插件仅初始化一级 data-bs-toggle,嵌套子菜单无事件绑定、定位失效且无障碍支持缺失;移动端须改用 collapse + 手动 JS 控制,严格分离响应式逻辑并同步 aria-expanded 与焦点。

Bootstrap 5 原生不支持手机端逐层折叠的多级下拉菜单——你写的 dropdown-menu 嵌套结构在移动端点开一级就全崩,二级三级直接悬空或错位,这不是 CSS 没写对,是框架压根没绑定子菜单事件。
为什么 data-bs-toggle="dropdown" 套娃在手机上完全失效
Bootstrap 的 Dropdown 插件只监听一级 data-bs-toggle="dropdown" 元素,所有嵌套在 .dropdown-menu 内部的子级 .dropdown 容器根本不会被初始化。手机点击父项时:
- 触发原生关闭逻辑,整个外层
.dropdown立即收起,子菜单连展示机会都没有 - 即使强行用
display: block强制显示,Popper.js不会为子菜单重新计算定位,大概率飞出屏幕右边缘 -
aria-expanded和焦点管理全部丢失,屏幕阅读器读不出“这个按钮展开的是子菜单”
手机端必须放弃 dropdown-menu 嵌套,改用 collapse + 手动 JS 控制
响应式多级下拉在移动端本质是「垂直折叠导航」,不是横向弹出。正确路径是把每级菜单转为 <div class="collapse">,并用 bootstrap.Collapse API 逐级实例化:
- 每个
.collapse必须有唯一id(如id="submenu-products"),不能共用data-bs-target - 用
new bootstrap.Collapse(element, { toggle: false })初始化,避免自动展开 - 监听父级按钮点击,调用对应子级
instance.show()或instance.hide() - 在
window.addEventListener('resize', ...)中检测window.innerWidth < 576,遍历所有已展开的非根级.collapse.show并调用instance.hide()
移动端必须手动同步 aria-expanded 和键盘焦点
仅控制显隐远远不够。手机端用户依赖屏幕阅读器和物理键盘导航:
- 每次调用
show()后,必须手动设置对应触发按钮的aria-expanded="true";调用hide()后设为"false" - 子菜单展开后,立即执行
firstFocusable.focus()(比如submenu.querySelector('button, a')),否则 Tab 键会跳过整块区域 - 监听全局
ESC事件,只关闭当前最深层级的展开项(用document.querySelectorAll('.collapse.show').length判断深度),而不是一股脑关掉所有 - 别漏掉
tabindex="-1"—— 子菜单容器本身要可聚焦,才能承接焦点
桌面端和手机端行为必须彻底分离
很多团队试图用同一套 JS 同时适配桌面 hover 和手机 click,结果两头都崩。真实项目中应明确切割:
- 桌面端:保留
:hover+ CSS 定位(left: 100%),禁用子菜单的 JS 展开逻辑 - 手机端:完全禁用
:hover样式,CSS 中用@media (max-width: 575.98px)将所有.dropdown-menu设为display: none,只启用.collapse流 - 不要试图在小屏下“模拟 hover”——手指没有悬停概念,任何延迟、过渡或间隙补丁都会导致误触或闪退
真正卡住的从来不是怎么写三层 HTML,而是每级 .collapse 是否拥有独立实例、是否在 resize 后被强制重置、以及 aria-expanded 是否与视觉状态 100% 同步。少一个环节,手机用户就可能卡死在半展开状态,且无法用键盘或语音操作退出。


















