多级导航菜单应将 nav 和 dropdown 拆为独立 block,dropdown 负责交互逻辑,nav 仅管布局与语义;子 dropdown 非 nav 的嵌套部分,而是独立实例,须避免跨 block 命名如 nav__item__dropdown。

多级导航菜单用 BEM 管理,核心不是“怎么写类名”,而是“哪些该拆成独立 block”。强行把所有层级塞进 nav__item__submenu__link 会立刻失控,必须按交互边界切分。
nav 和 dropdown 必须是两个独立 block
很多人把下拉菜单写成 nav__submenu 或 nav__dropdown,这是最常踩的结构性错误。dropdown 是可复用、可独立显隐、有自身过渡逻辑的交互单元,它和 nav 的职责不重叠——nav 负责整体布局与语义结构,dropdown 负责触发、面板定位、悬停/点击状态管理。
-
nav下只允许直接子元素:如nav__list、nav__item、nav__link - 所有下拉行为相关 DOM(触发按钮、面板容器、内部列表)必须包裹在
dropdownblock 内,例如:<div class="dropdown"> <button class="dropdown__toggle"></button><div class="dropdown__menu"></div> </div> - nav__item 内部只需放
dropdown实例,不掺杂任何 dropdown 的样式规则
多级嵌套时,子 dropdown 不是 nav__item__dropdown
二级菜单不是一级菜单的“子元素”,而是另一个独立的 dropdown 实例,只是它的触发位置嵌套在上层 nav__item 中。BEM 不允许跨 block 嵌套命名,否则就等于把两个职责耦合在一起,改一个 dropdown 动画会影响整个 nav 结构。
- 正确结构:
<li class="nav__item"> <a class="nav__link">产品</a><div class="dropdown">...</div> </li> - 错误写法:
nav__item__dropdown、nav__dropdown--level-2、dropdown__menu__submenu - 如果二级菜单需要不同动效或 z-index,直接加
dropdown--secondary这类 modifier,而不是靠父级前缀区分
hover 闪退、菜单消失快的根本原因是 DOM 关系断裂
浏览器里 .dropdown__toggle:hover + .dropdown__menu 失效,90% 是因为中间插了无关节点(比如 Vue 的 <transition></transition>、React 的 Fragment、或手写的 <div class="wrapper">),导致兄弟选择器断链。这不是 CSS 写得不对,是 HTML 结构没对齐 BEM 的假设前提。
<ul>
<li>确保 <code>dropdown__toggle 和 dropdown__menu 在 DOM 中是相邻兄弟节点,或 dropdown__menu 是 dropdown__toggle 的直接父级/子级(用 :has() 时注意兼容性)
dropdown__menu 设 min-width: max-content,防止内容收缩后鼠标移出热区remove() dropdown__menu 元素;用 display: none 或 visibility: hidden 切换更安全,BEM 类名才能持续生效React/Vue 中别用字符串拼接生成 BEM 类名
写 className={`nav__item ${isActive ? 'nav__item--active' : ''}`} 看似省事,实则埋下维护雷:modifier 状态分散在 JSX 各处,加个 --disabled 就要再补一串三元;更麻烦的是,一旦组件重构,block 名(如从 nav 改成 main-nav),所有拼接点都要手动搜改。
- 每个组件顶部定义常量:
const BLOCK = 'main-nav';,然后统一用${BLOCK}__item派生 - 状态类用对象语法绑定:
className={cx(`${BLOCK}__item`, { [`${BLOCK}__item--active`]: isActive })} - 禁止在 JS 里出现硬编码的
nav__link—— 它和组件声明的 block 名不一致,BEM 就失效了
真正难的不是记住双下划线和双破折号,而是每次加新菜单项时,能下意识问一句:“这个交互逻辑,能不能抽成独立 block?”——答案是“能”,就拆;答案是“不能”,说明它本就不该存在。











