bem 命名规范通过 block__element--modifier 结构明确样式归属,解决全局类名冲突与维护难题。例如 user-card__avatar--large 直观表明所属模块、元素及状态,禁用后代选择器与孤立 modifier,配合工具链校验,提升长期可维护性。

直接写 .header、.btn、.list 这类泛化类名,不是“写得快”,而是给后续所有人埋雷——维护成本会指数级上升,且越晚重构越疼。
为什么 .btn 这种写法上线后突然崩了
因为 CSS 是全局作用域,没有模块边界。你改的 .btn 可能同时匹配登录页的提交按钮、弹窗里的取消按钮、表格操作列里的导出按钮。谁先加载、谁后加载、有没有 !important、有没有第三方库注入同名规则,全靠运气生效。
- 常见错误现象:
DevTools里看到多个.btn规则叠加,权重相同,最终生效的是最后加载的那个——而这个“最后”在构建产物中可能随 chunk 分包顺序变化 - 排查时发现:改一个按钮圆角,结果后台菜单图标也变圆了;删掉一段旧 CSS,首页轮播图的
.next按钮却消失了(它其实复用了另一个模块的.btn) - 根本问题不在“写了多少”,而在“归属不明”:没人能一眼看出
.icon属于哪个组件、是否可安全删除
如何用 BEM 把类名变成可定位的“身份证”
BEM 不是加前缀,是让类名自带上下文:谁的、是什么、当前什么状态。比如 user-card__avatar--large,不用看 HTML 结构或 CSS 文件路径,就能确认它属于 user-card 模块,是头像元素,且处于大尺寸状态。
-
block必须语义明确且唯一:用search-bar,不用bar;用order-summary-table,不用table -
element用双下划线__连接,且必须是block的直接子语义:✅search-bar__input,❌search-bar__icon__close(应拆成search-bar__icon+icon--close或新 block) -
modifier用双短横--,只表达状态或变体:✅search-bar--compact、search-bar__input--error,❌search-bar__input-error(混淆了 element 和 modifier) - 禁止单独使用 modifier:
button--primary必须和button同时存在,否则基础样式丢失
老项目增量迁移到 BEM 的实操卡点
别重写整张样式表。重点是“让新增和修改的部分立刻符合 BEM”,旧代码可冻结,但禁止再往里面塞新的 .item、.content。
- 锁定高频冲突模块(如
header、modal、form-item),逐个切块升级:把散落的.input、.label、.error全部归入form-item__input、form-item__label、form-item__input--error - HTML 中每个容器只加一个
block类:<div class="form-item">,删掉所有孤立的 <code>.input规则,CSS 里只保留.form-item__input及其修饰符 - 禁用后代选择器模拟 BEM:
.form-item .input是反模式——它依赖 DOM 结构,且破坏 BEM 的可预测性;未来换成 Shadow DOM 或 CSS Modules 就直接失效 - 工具链兜底:用
stylelint-selector-bem-pattern在保存时校验类名格式;Vue/React 组件中通过bem('button', { loading: true })生成类名,避免手拼出button--+ type 导致空格或大小写错误
最常被忽略的一点:BEM 的价值不体现在“写的时候多打了几个字符”,而在于半年后你 grep user-card__avatar,能立刻定位到全部相关样式、JS 逻辑和测试用例——而不是翻 17 个文件,再猜哪段 CSS 真正在起作用。











