不该。hover 和 focus 应用原生伪类如 .btn:hover,而非 .btn--hover;后者违反 BEM 原则,不可编程、不可访问、易导致样式割裂;正确做法是统一定义基础伪类,Modifier 仅覆盖必要样式,并在需 JS 控制时用语义化类如 .btn--hovered。

hover 和 focus 该不该写成 .btn--hover 这类修饰符?
不该。这类命名违反 BEM 的核心原则:伪类是浏览器自动管理的瞬时状态,不是组件可编程、可预测的语义化状态。.btn--hover 无法被 JS 主动添加(:hover 不是 DOM 属性),也无法被屏幕阅读器识别,更会导致样式逻辑割裂——你得同时维护 .btn:hover 和 .btn--hover 两套规则,极易漏掉其中一套。
正确做法是只用原生伪类:.btn:hover、.btn:focus、.btn:active,且必须作用于 Block 或 Element 本身。
- 所有可交互 Block 都应定义基础伪类,哪怕只是继承默认行为(如
cursor: pointer) - Element 级伪类仅用于局部响应,例如
.card__close:hover,前提是card__close是独立可点击单元 - 禁用状态下取消 hover 效果,应写
.btn--disabled:hover { opacity: 1; cursor: not-allowed; },而不是删掉.btn:hover
多个修饰符叠加时,:hover 样式为什么容易失效?
因为层叠顺序和选择器权重不透明。比如你写了 .btn--primary:hover 和 .btn--disabled:hover,但漏了基础的 .btn:hover,那么非 --primary 的按钮就完全没悬停反馈;又或者 --disabled 规则里没显式重置 opacity 或 color,hover 时仍会透出上层样式。
根本解法是「基础 + 覆盖」结构:
立即学习“前端免费学习笔记(深入)”;
- 第一层:统一定义
.btn:hover,提供默认悬停表现 - 第二层:Modifier 只做必要覆盖,例如
.btn--disabled:hover { opacity: 1; } - 避免写
.btn--primary:hover这类耦合型选择器——颜色差异应由--primary本身决定,hover 只负责触发统一动效
需要 JS 主动控制 hover 行为(比如模拟悬停或移动端 fallback)怎么办?
这时必须引入一个语义清晰的临时 class,比如 .btn--hovered 或 .is-hovered,但注意:.is-hovered 不符合 BEM 命名习惯,推荐用 .btn--hovered —— 它是 Modifier,表示“当前被 JS 认定为悬停态”,和原生 :hover 并存不冲突。
关键点在于:这个 class 必须由 JS 显式增删,且要配合防抖、焦点同步、移动端 touch 事件处理:
- 鼠标:监听
mouseenter/mouseleave,加/删.btn--hovered - 键盘:监听
focus/blur,同样操作.btn--hovered(保持视觉一致性) - 移动端:用
touchstart模拟,但需pointer-events: none防止误触穿透 - CSS 中写
.btn--hovered, .btn:hover合并声明,避免重复
为什么 .card--expanded .card__header:hover 是错的?
它破坏了 BEM 的「无上下文依赖」原则。这个选择器隐含了 DOM 结构假设:.card__header 必须在 .card--expanded 内部;一旦组件结构微调(比如把 header 提到 card 外),样式立即失效。更严重的是,它让 hover 行为变成条件触发——只有展开时 header 才能悬停,这不符合用户直觉。
正确方式始终是直接挂载在目标元素上:
- ✅
.card__header:hover—— header 自身可交互,就给它独立 hover - ✅
.card--expanded .card__list—— 展开态影响的是 list 的显隐,这是 Modifier 的合理用途 - ❌
.card--expanded .card__header:hover—— 把结构、状态、交互三者强行耦合
真正容易被忽略的是:BEM 类名不是对 HTML 标签的映射,而是对组件职责的声明;hover 是元素自身的交互契约,不该被父级状态劫持。


















