bem通过类名自带作用域、避免嵌套和标签依赖来减少样式冲突与重构成本;块、元素、修饰符三级命名规范确保语义化与可维护性;工具链(stylelint、vs code插件)和工程化约束保障团队落地。

为什么BEM能减少样式冲突和重构成本
因为类名自带作用域,不依赖嵌套层级或标签类型。一旦组件挪动位置、改用不同HTML结构,.header__title--large 依然生效;而 .header .title 很可能直接失效。
常见错误现象:多人协作时,有人写 .user-card h3,有人写 .profile h3,最后发现两个模块的 h3 样式互相覆盖,查半天才发现是全局选择器惹的祸。
- 块(Block)是独立功能单元,如
card、modal,命名必须语义化且唯一 - 元素(Element)用双下划线连接,必须属于某个块,不能单独存在,例如
card__image合法,__image不合法 - 修饰符(Modifier)用双中划线,只描述状态或变体,如
button--disabled,不能包含样式值(禁止button--bg-blue)
CSS预处理器里怎么写才不破坏BEM语义
很多人用 Sass 的嵌套语法写成:
.card {
&__title { font-size: 1.2em; }
&--compact { padding: 0.5em; }
}
这看起来简洁,但实际编译后还是生成扁平类名,没问题;真正容易踩的坑是滥用 & 导致生成非BEM结构的类,比如:
.card {
&__content {
p { color: #333; } // 错!p 是标签选择器,破坏了BEM“只用类名”的原则
}
}
- 所有样式必须绑定到明确的 BEM 类,禁用标签、属性、伪类选择器(
:hover除外,可写在对应类内) - 修饰符要和基础类共存,
<div class="button button--primary">,不能只写 <code>button--primary - 避免用
@extend跨块复用,它会悄悄把其他块的类名注入当前选择器,污染作用域 - 用
stylelint-selector-bem-pattern插件,强制匹配正则^[a-z][a-z0-9]*(-[a-z0-9]+)*(__[a-z][a-z0-9]*(-[a-z0-9]+)*)?(--[a-z][a-z0-9]*(-[a-z0-9]+)*)?$ - VS Code 安装
BEM Helper插件,输入card回车自动补全card__header、card--fluid等候选 - 组件命名统一收口在项目级配置里,比如定义
const BLOCKS = ['avatar', 'tooltip', 'tabs'],CI 阶段校验新增类名是否在白名单中 - 用 CSS Scoped(Vue)或
:where()+ 前缀包裹第三方样式,例如:where(.legacy-wrapper) .el-form-item - 第三方组件尽量用 class 覆盖而非修改源码,覆盖类也走 BEM 命名,如
el-input--custom-size - Legacy 代码迁移时,先加一层 wrapper 元素并赋予 BEM 块名,再逐步把内部选择器转为元素类,避免一次性大改引发回归
如何让团队新人快速写出合规BEM类名
靠文档不如靠工具链。最直接有效的是在 ESLint + stylelint 中加规则约束。
常见错误现象:新人提交 userCardTitle 或 user-card-title,以为是驼峰或短横线就行,结果既不是 Block__Element 也不是 Block--Modifier。
遇到 legacy CSS 和第三方库怎么兼容BEM
没法重写整个 antd 或 Element Plus,但可以隔离影响范围。
典型场景:页面里既有自己写的 .form__item,又有 el-form-item,结果后者某些全局样式(如 el-form-item .el-input)意外影响了前者。
最麻烦的不是写新样式,而是判断哪些旧规则还在被 JS 动态添加或移除——比如某个 is-disabled 是 JS 注入的,就得同步加一条 button--disabled 规则去接管它。这事容易漏,上线后点不动按钮才想起来。











