BEM在项目出现类名冲突或样式覆盖时是必须采用的命名规范,它通过block__element--modifier格式明确模块归属与状态,解决多文件定义同名类、DOM层级依赖、响应式逻辑分散等问题,是多人协作和组件复用下的最小成本契约。

项目里出现类名冲突或样式覆盖时,BEM就不是“可选”,而是“必须”
当你在审查元素时发现同一个 .logo 类被 4 个不同文件定义,且修改一处导致弹窗、页头、侧边栏同时变形,说明项目已越过临界点。这不是个别疏忽,是缺乏命名契约的必然结果——类名没回答“属于哪个模块、当前什么状态”,浏览器和人都得靠猜。
常见错误现象:.logo 被 reset.css 重置宽高,header.scss 又给 img 加了 width: 100%,结果所有含 .logo 的地方都拉伸变形;DevTools 里样式来源横跨 common.css、modal.scss、profile.less 和第三方组件。
- 团队协作中,没人能确定
.card是该由卡片组件维护,还是被通用布局模块复用 - 改一个
padding触发连锁崩坏,不是因为代码写错,而是选择器依赖 DOM 层级(如.card .title),DOM 微调后样式直接失效 - 响应式逻辑散落各处:
is-mobile被加在按钮、表单、弹窗上,最后变成全局开关,改断点要grep -r "min-width"半小时
多人协作或组件复用超过 3 个模块时,BEM 是最小成本的协作契约
当项目开始拆分组件(比如 UserCard、SearchForm、ProductGrid),且这些组件被多个页面或微前端子应用引用,类名就必须自带上下文。否则,.button 在支付页和登录页含义不同,但 CSS 文件却混在一起引入,谁都不敢动。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 每个组件根元素类名必须是业务语义化的 Block 名:
user-card✅,card❌,profile-card❌(后者暗示父级存在,破坏独立性) - 子元素统一前缀:
user-card__avatar、user-card__name,禁止user-card__content__name(BEM 不允许双下划线嵌套) - 修饰符通过 props 拼接:
className={clsx('user-card', { 'user-card--loading': isLoading })},而非手写三元或内联字符串
构建工具报出 \.[a-z]\+ \.[a-z] 类选择器时,说明 BEM 已被绕开
用 grep -r "\.[a-z]\+ \.[a-z]" dist/ 扫描构建产物,只要命中,就代表存在 .card .title 这类嵌套选择器——它会让浏览器从右往左逐层回溯父节点,DOM 越深越卡,且无法被 tree-shaking 清理。
这类选择器往往来自误用预处理器嵌套:
-
.card { &__title { } }安全,编译为.card__title -
.card { & .card__subtitle { } }危险,编译为.card .card__subtitle,带空格 - Sass/Less 中所有
&后紧跟__或--才算合规
第三方库或 SSR 直出 HTML 时,BEM 是唯一不依赖 JS 的样式契约
当页面首屏由服务端直出,JS 尚未执行,user-card__avatar--loading 这种类名就是样式生效的唯一依据。它不靠 CSS Modules 的哈希、不靠 Vue scoped 的属性,也不靠 React 的 runtime class 注入——只靠名字本身表达“这是 user-card 块里的 avatar 元素,处于 loading 状态”。
容易被忽略的是:BEM 只管名字,不管谁覆盖谁。user-card__title--large 和 user-card__title--small 冲突,不是命名错,而是 CSS 引入顺序或层叠权重没管住。真正的卡点永远在文件组织和构建配置,不在 __ 和 -- 本身。


















