BEM 是协作底线而非命名洁癖,它通过 block__element--modifier 结构让类名自带归属、角色、状态三重语义,解决三人以上项目中类名混乱问题。

BEM 不是命名洁癖,是协作底线——没有统一标准的类名管理,在三人以上协作的 CSS 项目里,两周内就会出现 btn-primary、button--primary、ui-btn--active 同时生效却互不认领的情况。
为什么 BEM 能让团队真正“看懂彼此写的类名”
人脑不擅长记忆无上下文的字符串。看到 mt-4,得查文档才知道是 margin-top: 1rem;看到 card-title,得翻 HTML 才能确认它属于哪个 card;而 product-card__title--highlighted 本身已包含三重信息:归属(product-card)、角色(__title)、状态(--highlighted)。
常见错误现象:
- PR 里出现
user-card__title和user-card-title并存,审查元素时无法判断哪个是规范写法 - 设计师说“把所有主按钮变大”,结果有人改
.button--large,有人加.btn-lg,还有人直接写button { font-size: 1.2em } - 组件库升级后,旧项目里
modal__close突然失效——因为新版本改成了modal__trigger--close,但没人同步更新 HTML
关键不是“强制统一”,而是让每个类名自带可搜索、可推理、不可歧义的语义锚点。
立即学习“前端免费学习笔记(深入)”;
如何用工具链把 BEM 标准“焊死”在开发流程里
光靠 Code Review 拦不住手滑。必须让违规类名在保存、提交、构建三个环节就被拦截。
实操建议:
- 用
stylelint-selector-bem-pattern插件,配置{ "componentName": "[a-z][a-zA-Z0-9]+", "styleType": "bem" },自动报错.header .logo或.btn-primary - 在 Prettier 配置中禁用
htmlWhitespaceSensitivity的宽松模式,避免格式化后破坏class="block__elem block__elem--mod"的空格结构 - CI 流程中跑
npx stylelint "**/*.{css,scss}",失败则阻断合并——比人工检查快 10 倍,且不漏 case - 禁止在 JS 中拼接类名:
className={`card__body ${isCollapsed ? 'card__body--collapsed' : ''}`}改为用cn('card__body', { 'card__body--collapsed': isCollapsed })(配合clsx)
React/Vue 组件中怎么写才不破坏 BEM 完整性
BEM 类名一旦被 JS 动态拼接或条件覆盖,就脱离了 CSS 文件的静态定义,调试时 DevTools 里看到的类名和源码对不上,hydration mismatch 风险陡增。
使用场景:
- 组件级样式复用:一个
ProductCard组件,其所有子元素类名必须以product-card开头 - SSR 渲染一致性:服务端生成
product-card__price--sale,客户端必须渲染完全相同的字符串,不能依赖useId()或随机 hash - 微前端子应用隔离:子应用的
search-form__input不应被主应用的search-form样式意外覆盖
参数差异与坑:
- 不要在 JSX 里写
class="product-card__title"+class="text-lg font-bold"混合模式——后者是原子类,前者是语义块,二者层级冲突 - Modifier 状态应由组件 props 控制,而非 DOM 属性:
<ProductCard size="large" />→ 生成product-card--large,而不是监听data-size="large" - Element 名称禁止带 Block 语义:
product-card__product-card-title是冗余的,直接product-card__title
什么时候该停手,别硬套 BEM
BEM 解决的是「多人协作下类名归属模糊」的问题,不是所有 CSS 场景都需要它。
容易踩的坑:
- 纯工具类(如
u-text-center、visually-hidden)强行塞进 BEM 结构,变成utils__text-center,反而增加认知负担 - 全局主题开关(如
theme-dark)写成app--dark,但app并非真实 Block,也不可复用 - CSS-in-JS 项目里,用
styled.div已实现作用域隔离,再套 BEM 类名(如const Card = styled.div.attrs({ className: 'card' }))属于重复抽象 - 营销页、落地页等单页项目,DOM 结构固定、无复用需求,用
.hero-section__headline不如直接.hero-headline
真正要盯住的,是那些跨组件、跨仓库、跨团队复用的 UI 模块——它们的类名一旦失控,修复成本远高于初期约束成本。


















