BEM 通过 block__element--modifier 结构实现样式隔离,避免类名冲突。不同 Block 下同名 Element(如 .user-card__avatar 和 .profile-card__avatar)互不干扰,因 Block 构成天然命名空间;语法契约(__ 和 --)是工具链识别基础,需用 stylelint 等强制校验;禁止后代选择器和手拼类名,推荐封装生成函数或 CSS Modules;团队协作需白名单与作用域声明保障可追溯性。

直接写 .button 或 .header 必然冲突,BEM 不是靠“名字更长”防撞车,而是用 block__element--modifier 把归属、层级、状态全锁进类名里——冲突不再随机发生,而变成一眼可查的结构错误。
为什么 .user-card__avatar 和 .profile-card__avatar 不会互相覆盖
BEM 的 Block 名(如 user-card)天然构成命名空间,两个不同 Block 下的同名 Element(__avatar)编译后就是完全不同的类名,浏览器根本不会匹配到同一个规则。这不是运气好,是结构强制隔离。
-
.user-card__avatar只响应user-card模块内定义的样式,哪怕profile-card也写了__avatar,它们互不感知 - 常见错误:把
.avatar当通用类复用,结果改一处圆角,三处头像全变;或写.card .avatar这种后代选择器,DOM 加个 wrapper 就失效 - Element(
__avatar)不能脱离 Block 单独写样式,否则既违反 BEM 原则,又失去复用前提
构建层必须校验 __ 和 -- 的语法契约
__ 和 -- 不是风格偏好,是工具链识别 BEM 结构的硬性锚点。所有 lint 工具、VS Code 插件、CSS 提取逻辑都靠它做正则匹配,写错就等于关掉防护。
- 错误写法:
.card-header--large(漏掉一个_)、.button__icon_error(用单_代替--)、.card__header--large--dark(嵌套修饰符) - 正确做法:配置
stylelint-selector-bem-pattern,规则设为{"styleType": "bem"},CI 中跑npx stylelint "**/*.{css,scss}"阻断违规提交 - 禁止在 SCSS 中嵌套出后代选择器:
.user-card { .avatar { } }→ 编译出.user-card .avatar,破坏封装性且无法反推来源
JS 动态拼接类名时最容易踩的坑
手拼字符串看着快,实际在 CI 环境下极易因空格、连字符、大小写漏写而挂掉,本地开发跑得通,Linux 构建直接报错。
立即学习“前端免费学习笔记(深入)”;
- 危险写法:
className={`button button--${variant} ${hasIcon ? 'button__icon' : ''}`}(缺空格、button__icon脱离 block、无防错) - 推荐封装常量:
const BLOCK = 'search-form'; const cn = (e, m) => `${BLOCK}${e ? '__' + e : ''}${m ? '--' + m : ''}`,调用cn('input', 'disabled')得到search-form__input--disabled - 若用 CSS Modules,坚持
import styles from './Button.module.css'+className={styles['button--loading']},永远不硬写哈希类名
多人协作时真正起作用的是白名单 + 作用域声明
光靠人写对 BEM 名没用,必须让工具守住边界。团队不敢删旧类,不是因为技术限制,而是没人敢确认哪个页面还依赖它——这说明缺乏可追溯的归属机制。
- 用
postcss-bem-linter配置componentName白名单,比如限定 block 只能是['button', 'card', 'search-form'],防止随手写.my-button - 所有新组件 CSS 文件开头加注释声明作用域:
/* @scope product-list */,CI 中扫描该标记做归属校验 - 禁用全局 CSS 文件(如
base.css)中出现__或--,这类文件只放html、body、h1等基础重置
最容易被忽略的不是怎么写 BEM,而是每次写 class 前是否真问过:“这个样式属于谁?它会不会在别的上下文里意外生效?”——这句话比任何工具配置都管用。


















