@at-root仅提级紧随其后的规则,不穿透嵌套、不继承变量、不恢复&上下文;默认只脱离选择器嵌套(without: rule),需(without: all)才脱离媒体查询等所有作用域。

@at-root 不是“退出嵌套”的快捷键,而是显式控制编译时输出层级的指令;它只对**直接包裹的选择器规则**生效,且默认仅跳出选择器嵌套(without: rule),不脱离 @media、@supports 或其他作用域。
为什么 .modal { .is-loading { @at-root .is-loading { ... } } } 仍生成 .modal .is-loading
这段代码里,@at-root 被写在了 .modal → .is-loading 的嵌套路径内部,Sass 不会“向上追溯”去提级——它只作用于紧贴其后的那条规则,而该规则本身仍处于 .modal 块的作用域中。结果就是:编译器把 .is-loading 当作普通子选择器处理,@at-root 根本没被触发提级逻辑。
正确做法只有两种:
- 把
@at-root .is-loading { ... }拿到.modal块外部,作为顶层规则书写 - 或改用
@at-root (without: all) .is-loading { ... }强制剥离所有包裹层(包括父选择器和媒体查询)
@at-root 默认行为 vs (without: all) 的实际差异
裸 @at-root 等价于 @at-root (without: rule),只跳过选择器嵌套,但卡在当前 @media 或 @supports 内部。比如:
立即学习“前端免费学习笔记(深入)”;
@media (min-width: 768px) {
.card {
@at-root .is-expanded { opacity: 1; }
}
}
编译后是:@media (min-width: 768px) { .is-expanded { opacity: 1; } }——这个类在小屏下完全不可用。
要让它真正全局可用,必须写:
@media (min-width: 768px) {
.card {
@at-root (without: all) .is-expanded { opacity: 1; }
}
}
这时才生成独立的顶层 .is-expanded 规则,不受媒体查询限制。
BEM 场景下误用 & + @at-root 导致选择器语义错乱
常见错误写法:.block { &__text { @at-root .block--dark & { color: #fff; } } }。这里 & 在 &__text 块内指向的是 .block__text,所以最终生成 .block--dark .block__text,而非预期的 .block--dark .block__text 与 DOM 结构强耦合(要求 .block--dark 必须加在 .block__text 父元素上)。
更安全、更符合 BEM 意图的写法是:
- 显式写出完整类名:
@at-root .block--dark .block__text { color: #fff; } - 或把修饰类提至顶层:
.block--dark { @at-root .block__text { color: #fff; } }(前提是.block--dark确实作用于块根节点)
在 @each 或 @mixin 中使用 @at-root 的作用域陷阱
@at-root 在循环或混入中不会自动继承局部变量。例如:
@each $theme in (light, dark) {
$class: "theme-#{$theme}";
@at-root .#{$class} { color: red; }
}
这段能正常工作,因为插值 #{$class} 是在 @each 作用域内求值的;但若改用 @let $class: "theme-#{$theme}";(Sass 1.50+),@at-root 内部就访问不到 $class —— 它已超出当前作用域链。
另一个关键限制:& 在 @at-root 内部无效。写 .card { @at-root &__header { ... } } 会报错,因为 & 依赖嵌套上下文,而 @at-root 已清除该上下文。
真正容易被忽略的是:你写的 @at-root 是否真的出现在它该出现的位置——它不“穿透”嵌套,也不“继承”变量,只忠实地提级它眼前那一条规则。任何想靠它“绕过作用域”或“动态拼接父类”的尝试,大概率会产出意料之外的选择器结构。


















