bem是一种通过block__element--modifier三段式命名实现css作用域隔离的规范。它强制类名体现归属关系,避免样式耦合;要求css选择器严格对应类名、禁止嵌套;元素须依附block,修饰符必须与主体共存;多层结构应拆分为独立block而非深层嵌套;动态生成需校验modifier值,防止非法类名。

什么是BEM,为什么不能直接套用 class="btn primary big"
因为这种写法会让样式耦合、难以维护,一旦primary在别处被复用,就可能意外覆盖btn的样式。BEM强制用三段式结构把「归属关系」写进类名里:block__element--modifier。它不是语法糖,而是靠命名约定实现作用域隔离——没有预处理器、不依赖构建工具也能生效。
常见错误是把header__logo--large写成header-logo-large(少了双下划线和双短横),结果 CSS 选择器对不上,样式完全不生效。
-
block是独立功能单元,比如card、menu,不能嵌套其他 block -
__element必须依附于 block,不能单独存在,例如card__title合法,__title非法 -
--modifier只能修饰 block 或 element,且必须和它们共存,card--featured或card__button--disabled可以,但featured单独用不行
怎么写 CSS 选择器才不漏掉 BEM 层级
BEM 的 CSS 写法和 HTML 类名严格对应,不需要嵌套或后代选择器。写错一个符号,样式就断掉——比如把.card__content写成.card_content(单下划线),浏览器就当它是另一个类。
推荐直接按 HTML 中出现的完整类名写选择器,不加空格、不加层级:
.card { }
.card__title { }
.card__content { }
.card--featured { }
.card__button--disabled { }
避免这样写:
.card .card__title { } /* 多余的空格,破坏 BEM 独立性 */
.card__title h2 { } /* 用标签名绑定,违背语义化 */
- 所有选择器都以
.开头,且只包含一个类名(BEM 全称) - 不要用
div.card__title这类标签限定,万一换成span就失效 - 修饰符必须和主体共存,
card--featured card__title这种组合写法是 OK 的,但不能只写card__title--featured去替代card--featured
遇到多层嵌套组件,BEM 怎么避免类名爆炸
真实项目里常有sidebar > list > item > avatar > image这种结构,如果硬套 BEM,类名会变成sidebar__list__item__avatar__image——这违反了 BEM 原则。正确做法是:每个独立视觉单元都是一个 block。
比如avatar本身可复用,就该定义为独立 block:avatar,而不是sidebar__list-item__avatar。
- 判断标准:这个元素是否会在别处重复使用?能,就抽成 block;不能,才作为 element
-
list__item可以,但list__item__avatar不行;应该写成list__item+avatar - block 之间允许组合使用,比如
<div class="list__item avatar">,只要 CSS 里<code>.avatar不依赖.list__item的样式即可 - 如果
avatar在list__item里有特殊尺寸,用修饰符:avatar--in-list,而不是强行拉长命名 - 用对象语法更安全:
{ 'card--featured': isFeatured, 'card__button--disabled': isDisabled } - Element 名称别拼错:写
card__contnet(少个 i)比写错 CSS 还难排查 - 工具推荐:Vue 可用
vue-bem-cn,React 可用bem-cn,但别盲目引入——简单场景手写对象足够 - 服务端渲染时注意 modifier 值是否来自用户输入,防止注入非法字符到类名中
Vue/React 里动态拼接 BEM 类名容易出什么错
模板中手写:class="['card', `card--${type}`, `card__button--${status}`]"看着没问题,但一旦type为空或undefined,就会生成card--这种非法类名,CSS 无法匹配,还可能触发意料外的样式。
更隐蔽的问题是 modifier 没做布尔判断,比如card__content--hidden本该只在isHidden为 true 时出现,但写了`card__content--${isHidden}`,结果输出card__content--true或card__content--false。
BEM 的核心不是多写几个下划线,而是让每个类名都能回答三个问题:它属于哪个功能块?它在这个块里是什么角色?它当前处于哪种状态?漏掉任意一个,后续改起来就只能靠猜。











