BEM 通过单一顶层类名和显式子元素类名(如 card__title)切断 CSS 对 DOM 结构的依赖,避免样式污染与定位漂移;禁止深层嵌套、伪类驱动状态及语义类名复用,强调类名即结构契约。

为什么直接写 .header .logo 会让组件越来越难维护
因为样式污染和定位漂移:外部样式可能意外覆盖内部元素,而组件一旦被复用到不同上下文(比如嵌套在 .modal 或 .sidebar 里),.header .logo 就可能匹配到非本意的节点。BEM 的核心不是“多写类名”,而是切断 CSS 选择器对 DOM 结构的依赖。
实操建议:
- 每个组件只用一个顶层
class作为命名空间,例如card,所有子元素都基于它显式声明,不靠父子关系推导 - 子元素必须带双下划线,如
card__title、card__footer;修饰符用双破折号,如card--featured - 禁止出现三层及以上的嵌套类名(比如
card__header__title),那说明结构已超限,该拆成新组件
BEM 类名怎么对应真实 HTML 结构
不是“把结构翻译成类名”,而是用类名定义结构契约。只要类名存在,就代表该语义角色必须存在——哪怕它只是个空 <div>。
常见错误现象:
立即学习“前端免费学习笔记(深入)”;
- 写
<div class="card"><h2 class="card__title">…</h2></div>,但漏掉card__body,导致后续想加 body 样式时不敢动card__title的 margin - 用
card__title card__subtitle这种“复用类名”方式,破坏了 BEM 的唯一性原则,让card__subtitle失去独立样式控制能力
正确示例:
<article class="card">
<header class="card__header">
<h2 class="card__title">标题</h2>
</header>
<div class="card__body">内容区</div>
<footer class="card__footer">操作按钮</footer>
</article>
遇到伪元素或状态类(:hover, :disabled)怎么处理
BEM 不排斥 CSS 状态伪类,但禁止用它们替代语义类名。比如不能靠 .card:hover .card__title 控制悬停变色,而应提前定义 card__title--hovered 并由 JS 切换。
原因很实际:
- CSS 无法在伪类中触发 transition 的 class 切换,容易造成动画卡顿
- 服务端渲染或 SSR 场景下,
:hover无法预判,样式不可控 - 可访问性工具(如屏幕阅读器)通常不识别伪类状态,而
card__title--focused可以配合tabindex和 JS 显式管理
性能提示:大量使用 :not() 或 :has() 会显著拖慢重排,BEM 的扁平类名天生规避这类问题。
和 CSS-in-JS 或原子化方案(Tailwind)混用时要注意什么
可以共存,但边界必须清晰。BEM 负责语义层级和组件契约,其他方案负责视觉细节。
容易踩的坑:
- 在 React 中用
clsx拼接card__title和text-lg font-bold,结果text-lg覆盖了card__title的行高设定,且无法从组件层追溯 - 用 Tailwind 的
group-hover:去驱动card__title变色,绕过了 BEM 的状态命名,导致同一组件在不同框架中行为不一致
推荐做法:BEM 类名只出现在 className 的最左侧,原子类放右侧,例如 className="card__title text-base group-hover:text-blue-600",并确保 card__title 自身已定义基础排版(padding、line-height 等)。
真正麻烦的是跨团队协作时,有人坚持“BEM 只写结构”,有人默认“BEM + Tailwind = 全能”,没人管 card__title 的字体大小到底该谁定——这个边界不写进 README,三个月后就会变成玄学。


















