根本原因是 .dropdown-item 缺少 position: relative,导致子级 .dropdown-menu 的 left: 100% 相对于 body 定位而飞出视口;必须显式设置该样式,并确保子菜单为直接子元素、正确添加 data-bs-toggle、手动覆盖 CSS 实现右向展开及 z-index 层级,移动端需用 JS 替代 hover,三级菜单须特别处理定位、z-index 和 ARIA 属性。

为什么 .dropdown-item:hover .dropdown-menu 不生效
根本原因不是选择器写错,而是父级 .dropdown-item 缺少 position: relative。Bootstrap 5 默认没给它设定位上下文,导致子级 .dropdown-menu 的 left: 100% 相对于 body 定位,直接飞出视口右侧,肉眼看不到。
必须显式加这条规则:
.dropdown-item {
position: relative;
}
- 子菜单必须是
.dropdown-item的**直接子元素**,不能是兄弟节点或隔层嵌套 - 别用
.dropdown-menu:hover .dropdown-menu—— 子菜单自己 hover 时已脱离触发源,逻辑断裂 - 确保 HTML 中没有遗漏
data-bs-toggle="dropdown"的一级菜单项(否则整个下拉链路断开)
二级菜单向右展开的 CSS 补丁
默认所有 .dropdown-menu 都向下展开,二级需强制右向定位。仅靠 dropend 类无效,它只作用于一级且被 Popper 封装,无法传导到嵌套层级。
关键样式必须手动覆盖:
立即学习“前端免费学习笔记(深入)”;
.dropdown-menu .dropdown-menu {
position: absolute;
top: 0;
left: 100%;
margin-top: -2px;
margin-left: -4px;
z-index: 1001;
display: none;
}
.dropdown-item:hover > .dropdown-menu,
.dropdown-item:focus > .dropdown-menu {
display: block;
}
-
margin-left: -4px是为了填补鼠标移入时的视觉间隙,避免闪退 -
z-index: 1001必须比父级.dropdown-menu(默认z-index: 1000)高,否则会被遮盖 - 如果父容器用了
transform(如.navbar-collapse动画),会创建新层叠上下文,此时子菜单的z-index只在该上下文内生效,得把z-index提到同级容器上
移动端失效?别只靠 :hover
iOS 和 Android 上 :hover 行为不可靠:轻点可能短暂触发、长按无反应、Tab 键焦点完全进不去。纯 CSS 方案在小屏等于不可用。
- 用媒体查询禁用小屏 hover:
@media (max-width: 991.98px) { .dropdown-item:hover > .dropdown-menu { display: none; } } - 改用 JS 控制
.show类切换,监听click而非mouseenter - 不要全局设
pointer-events: none—— 这会让子菜单里所有链接失活 - 若用 JS,需手动调用
bootstrap.Dropdown.getOrCreateInstance(el).show(),不能只依赖data-bs-toggle="dropdown"堆叠
三级菜单的定位和 z-index 容易被忽略
三级菜单不是“二级再复制一遍”,.dropdown-menu .dropdown-menu .dropdown-menu 这类选择器看似递归,实际会踩坑:定位偏移放大、z-index 难以逐层递增、悬停断连更严重。
- 三级菜单建议统一用
position-static+ 显式left: 100%,避免 Popper 错位干扰 - z-index 至少设为
1002,但更稳妥的是提级到同层容器(比如挂到.navbar下,而非嵌套在二级菜单内部) - 原生箭头伪元素(
::before)在三级会错位,必须显式隐藏:.dropdown-submenu .dropdown-menu::before { display: none; } - 最易被忽略的是 ARIA 属性 —— 纯 CSS 无法自动补
aria-expanded和aria-haspopup,键盘导航和屏幕阅读器会直接跳过子菜单


















