bem层级深是误解,真正问题在于用__强行模拟dom父子关系;card__header__title违反语义边界,应禁用;复用性与独立边界是抽新block的唯一标准。

直接说结论:BEM结构本身不会层级过深,所谓“BEM层级深”是误把DOM嵌套当成了BEM层级——真正该砍的不是__或--的数量,而是用__强行模拟HTML父子关系的行为。
为什么card__header__title这种写法必须禁掉
它不是语法错误,而是语义越界。BEM只允许block__element和block--modifier两种形式,card__header__title中第二个__意味着你把card__header当作新Block,但它只是card的一个Element,没有独立边界、不可复用、无法脱离DOM路径存活。
- SCSS里写
&__header { &__title { } }会编译出非法类名,且容易误给card__header加padding,破坏Element“纯展示”的职责 - 一旦
card__header结构改成card__section,所有card__header__title静默失效 - 想在弹窗里复用这个标题?不行,它绑死了“card → header → title”的DOM链
遇到“子元素的子元素”时,该抽新Block还是加Modifier
判断标准只有一个:这个UI片段会不会在别处复用?有没有自己的逻辑或样式边界?
- 如果
list__item__text只出现在卡片里,且绝不出现在用户列表、搜索结果页——那就用card__body-text--subtle或is-in-card-body这类State类 - 如果
list__item需要独立交互(点击展开)、有自己的动画或状态管理——立刻抽成listBlock,类名改为list__item、list__item__text就合法了 - 修饰符不能补层级缺口:
card__header__title--small是错的,等于把两层语义塞进一个Modifier;应拆成card__header-title+card__header-title--compact
SCSS嵌套里&前后空格决定输出是否合规
&只代表上一层的选择器字符串,和缩进无关,只看空格。这是90%的BEM类名错乱根源。
-
.card { .card__header { &__title { } } }→ 编译出.card__header__title(&指向.card__header,非法) -
.card { &__header { &__title { } } }→ 也出.card__header__title(语法合法但违反BEM) -
.card { &__header { } &__title { } }→ 正确输出.card__header和.card__title两个平级类 - 真要保留外层上下文?用变量:
$block: ".card"; #{$block}__title { color: red; }
第三方组件库混用BEM时最容易忽略的权重陷阱
你没法改ant-design的.ant-btn,但可以隔离作用域——关键是别让BEM类名被第三方样式意外覆盖或反向污染。
- 禁用全局重置,改用
all: unset在Block范围内清空,再逐个声明需要的样式 - 避免用
!important覆盖第三方样式——它污染整个继承链;改成更具体的BEM组合,如my-form__submit-button my-form__submit-button--loading - 用
:where()或CSS Modules降低权重干扰,比如:where(.my-card__content) { ... }不增加选择器权重 - JS里硬编码的选择器如
document.querySelector('.card .header .title')必须立刻替换为document.querySelector('.card__header-title'),否则重构时根本不敢动DOM
真正难的不是记住__和--怎么写,而是每次往HTML里加一层<div>时,停下来问一句:这个节点是不是一个能独立存在、有明确功能边界的UI单元?如果不是,它就不该有自己的BEM类名——更不该靠多一个<code>__来“假装”合理。











