@at-root #{&}__element是唯一安全生成bem扁平类名的方式,因&代表完整选择器字符串,直接&__title会编译为.card .card__title(带空格后代选择器),违背bem单类名要求;必须用@at-root提级并插值拼接。

@at-root #{&}__element 是唯一能生成合法 BEM 扁平类名的方式,其他写法都会产出带空格的后代选择器,直接违背 BEM 原则。
为什么 &__title 会编译成 .card .card__title 而不是 .card__title
SCSS 的 & 代表当前完整选择器字符串,不是“块名”。在 .card { &__title { } } 中,& 确实是 .card,但拼接时保留空格,结果就是后代选择器 .card .card__title。浏览器匹配它需要向上查找父级,BEM 要求的是原子类名 .card__title —— DOM 上必须显式存在这个 class,否则样式不生效。
常见错误场景包括:
-
.card { .header { &__title { } } }→ 编译为.card .header__title,语义错乱 -
.button { &:hover { &__icon { } } }→ 编译为.button:hover__icon,非法类名,浏览器直接忽略 - 父选择器本身含空格(如
.sidebar .widget),&__content就变成.sidebar .widget__content,已不是合法 BEM
正确用法:@at-root #{&}__element 必须写在块顶层
只有把 @at-root 和插值 #{&} 配合使用,并放在块级选择器的最外层,才能安全提取纯块名、零空格拼接。
示例:
.card {
@at-root #{&}__header { color: #333; }
@at-root #{&}--loading { opacity: 0.6; }
}
输出为:
.card__header { color: #333; }
.card--loading { opacity: 0.6; }
关键约束:
- 不能嵌套在
&:hover、&[disabled]、@media或任何子容器内,否则&含义错位或输出顺序不可控 - 块名若为变量(如
$block: "modal"),改用@at-root #{$block}__close { } - 修饰符(
--)和元素(__)必须与块同级,不能出现在&__header内部再写&--large
@each 批量生成修饰符时字符串引号不能漏
用 @each 生成 .btn--hover、.btn--disabled 这类修饰符,最容易踩的坑是忘记给修饰符加引号。
错误写法:@each $modifier in (hover, disabled) → Sass 报错 Undefined variable "$hover",因为括号内未加引号会被当作变量名解析。
正确写法:
$block: "btn";
@each $modifier in ("hover", "disabled", "large") {
@at-root #{$block}--#{$modifier} {
/* 样式 */
}
}
如果从 map 取值(如 map-keys($modifiers)),需确认 key 是字符串类型;否则返回的是无引号标识符,仍需手动转字符串。
真正容易被忽略的点:BEM 解耦不靠 SCSS 语法,靠 HTML 和 CSS 类名严格对齐
即使 @at-root #{&}__title 编译完全正确,只要 HTML 中漏写了 class="card" 或只写了 card__title 没写 card,样式就彻底失效。调试时你看到的是“样式没应用”,但根源是 DOM 缺 class,不是逻辑错误。
BEM 的扁平性不是为了写起来方便,而是切断浏览器匹配路径依赖——每个类名必须独立可识别、可搜索、可复用。一旦你在 SCSS 里用 & 拼出带空格的选择器,或者让修饰符嵌套进伪类内部,就等于悄悄退回了结构依赖的老路。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











