BEM不支持“块→元素→元素”三层结构,card__header__title是非法命名;元素必须直接隶属于Block,嵌套内容应拆分为独立Block或用属性/State类表达上下文关系。

孙子元素不是BEM的合法层级
BEM明确不支持“块→元素→元素”这种三层结构,card__header__title 是非法命名。它违反了BEM最根本的设计前提:每个元素必须直接隶属于一个Block,不能是另一个Element的子级。
常见错误现象包括:开发者看到DOM嵌套深(比如 <div class="card"><div class="card__header">
<h2 class="card__header__title">),就顺手照抄结构写类名;或用SCSS多层嵌套生成这类类名,结果样式不可复用、无法独立测试。<ul>
<li>Element 必须语义上属于 Block 本身,而非某个 Element 的视觉延伸</li>
<li>所谓“孙子”,实际要么是新 Block,要么是 State 类,极少是真正需要独立样式的 Element</li>
<li>强行命名会导致类名爆炸、CSS选择器权重失衡、组件抽离困难</li>
</ul>
<h3>遇到嵌套内容,优先拆成独立 Block</h3>
</h2>
<p>比如表格单元格里有个状态徽标:<code><td class="table-cell"><span class="table-cell__badge">new</span></td> —— 这里的 table-cell__badge 就是典型误用。Badge 不依赖于 cell 才存在,它在用户头像、订单卡片、通知栏里都会出现。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 把
badge定义为独立 Block:.badge {} .badge--new {} .badge--error {} - 需要表达上下文关系时,用属性或父级类名控制样式,例如:
.table-cell .badge { margin-left: 4px; }或[data-in="table-cell"] .badge { … } - React/Vue中可通过 props 注入 context 类名,避免硬编码
table-cell__badge
真需表达视觉父子关系,用属性选择器或 State 类替代
当某元素确实在多个场景下只作为“子中子”出现,且样式强依赖父容器(比如弹窗里的按钮组、折叠面板里的二级标题),又不想新增 Block 时,BEM推荐用更可控的方式表达关系:
- 用
data-属性标记上下文,如<h3 data-section="accordion" class="heading"></h3>,再写[data-section="accordion"] .heading { … } - 用通用 State 类,如
is-subheading、is-nested,它们可跨 Block 复用,且不污染 Block 命名空间 - 避免使用
card__content__subtitle这类命名——它既难搜索,又让subtitle丧失独立性
SCSS 中禁止超过一层嵌套生成 BEM 类名
SCSS 的 & 符号容易诱使人写出两层嵌套:
.card {
&__header {
&__title { /* 错!生成 .card__header__title */
color: #333;
}
}
}这会悄悄产出非法类名。正确写法是只用一层 &:
.card {
&__header { … }
&__title { … } /* 标题是 card 的元素,不是 header 的 */
}如果真需要 header 下的 title 有特殊样式,优先考虑:
- 加 Modifier:
.card__header--has-title .card__title - 用组合类:
.card__header .card__title(注意这不是BEM类名,只是选择器) - 或者承认:这个 title 其实该属于
headerBlock,那就该叫header__title
复杂点在于:很多人把“DOM嵌套深”等同于“语义层级深”,但BEM只认语义,不认DOM。这点最容易被忽略,也最难扭转习惯。


















