还在为样式嵌套依赖 Sass/SCSS 而烦恼?CSS 嵌套(CSS Nesting)作为原生 CSS 能力,已经在 Chrome 112、Safari 16.5、Firefox 117 之后的版本全面落地。这意味着你可以像写预处理器那样,在一条规则里嵌套子选择器、用 & 引用父级、甚至把媒体查询写进选择器内部,而完全不需要构建步骤。本文用可复制的代码示例,带你把一段典型的 SCSS 重构成纯 CSS 嵌套,并给出浏览器支持现状、回退方案与常见坑位清单。
一、为什么需要原生 CSS 嵌套
组件化开发让样式层级越来越深:卡片里套标题、标题里套标签、标签里再套图标。传统 CSS 只能把每一个层级平铺成独立选择器,写起来冗长、读起来割裂。预处理器(Sass/LESS)用嵌套解决了可读性,却强制引入构建链路。原生 CSS 嵌套的目标正是:在不增加工具链的前提下,把“就近书写”的体验还给纯 CSS。
它的价值不只是少写几行。嵌套让“一个组件相关的样式”物理上聚集在一起,改一处不会牵一发而动全身;配合后面讲到的元素级媒体查询,响应式逻辑也能和组件本体同框。对于已经在用 Vue 3 组合式 API 实战 这类现代框架的团队,样式层同样值得一次现代化升级。
二、基础语法:选择器直接嵌套
最直观的用法是:把子选择器直接写进父规则的大括号里,父选择器会被自动前缀到每个嵌套选择器之前。
示例 1:平铺 vs 嵌套,二者最终匹配的 DOM 结构完全一致
/* 传统平铺写法 */
.card { padding: 16px; }
.card .title { font-weight: 600; }
.card .title span { color: #888; }
/* CSS 嵌套写法 */
.card {
padding: 16px;
.title {
font-weight: 600;
span { color: #888; }
}
}注意:嵌套时父选择器是“隐式”前缀的,等价于在子选择器前补上 .card。正因为隐式前缀是“后代匹配”,所以嵌套层级不要过深(见第十节),否则生成的选择器会意外匹配到不相关的深层元素。
三、& 符号:显式引用父选择器
当隐式前缀不够用时,用 & 显式代表“父选择器整体”。它常出现在伪类、修饰类、直接子元素组合等场景,让嵌套既能贴着父级、又能精确控制关系。
示例 2:& 用于伪类、修饰类与直接子元素
.button {
padding: 8px 16px;
&:hover { background: #2563eb; } /* 等价于 .button:hover */
&.primary { background: #2563eb; color: #fff; } /* 修饰类,不是后代 */
& > .icon { margin-right: 8px; } /* 仅直接子元素 .icon */
&:not(:disabled) { cursor: pointer; }
}3.1 后缀 & 的兼容性提醒
规范早期允许把 & 写在选择器末尾(如 a { :hover& { color: red; } } 等价于 a:hover)。但“后缀 &”在部分浏览器支持较晚,且可读性差,建议一律用前缀 & 表达,避免发布后在某些内核上静默失效。
四、嵌套媒体查询:把断点写进组件
原生嵌套最惊艳的能力之一,是媒体查询可以内嵌在任意选择器里。响应式断点不再集中堆在文件末尾,而是和组件样式“就地”共存,维护时一眼就能看全某组件在各尺寸下的表现。
示例 3:元素级响应式,断点与组件同框
.card {
padding: 16px;
font-size: 14px;
@media (min-width: 768px) {
padding: 24px;
font-size: 16px;
.title { font-size: 20px; }
}
}五、复杂选择器与多重 &
在嵌套里,& 代表父选择器整体,因此可以在一个选择器里多次出现,也能和兄弟、相邻组合子搭配,实现“自己引用自己”的精确匹配。BEM 风格的 &__list 这种拼接写法也是合法的。
示例 4:多重 & 与 BEM 拼接
.nav {
& .item + & .item { margin-left: 8px; } /* 等价于 .nav .item + .nav .item */
&__list { display: flex; } /* 等价于 .nav__list */
&.is-open &__list { display: block; }
}六、@nest 与规范演进(了解即可)
早期草案曾引入 @nest 规则来承载“不能简单前缀”的嵌套(例如嵌套一个不以父级开头的兄弟选择器)。但规范最终统一为“直接嵌套 + & 引用”,@nest 已不再被推荐使用。新项目直接写原生嵌套即可,无需再引入 @nest 语法。
七、浏览器支持现状
截至 2024 年起,主流引擎均已默认开启 CSS 嵌套,无需开启任何实验标志。下面是关键版本的落地节点:
| 浏览器 | 最低支持版本 | 状态 |
|---|---|---|
| Chrome / Edge | 112 | ✅ 默认支持 |
| Safari | 16.5 | ✅ 默认支持 |
| Firefox | 117 | ✅ 默认支持 |
| iOS Safari | 16.5 | ✅ 默认支持 |
| Node(cssnano 等) | — | 构建期可降级 |
如果你的用户群体仍需兼容更老的浏览器(例如企业内网锁定旧版内核),请跳转第九节的渐进增强方案,用特性检测做平滑回退。
八、实战:把 SCSS 组件重构成原生 CSS 嵌套
下面用一个产品卡片组件演示迁移。你会发现,除了去掉了 $ 变量与 mixin,结构几乎一一对应,迁移成本极低。
重构前:SCSS 写法
// 重构前(SCSS)
.product {
border: 1px solid #e5e7eb;
&__head { display: flex; align-items: center; }
&__title {
font-size: 18px;
&:hover { color: #2563eb; }
}
@media (max-width: 600px) { padding: 8px; }
}重构后:纯 CSS,构建链路可省
/* 重构后(原生 CSS 嵌套,无需预处理器) */
.product {
border: 1px solid #e5e7eb;
&__head { display: flex; align-items: center; }
&__title {
font-size: 18px;
&:hover { color: #2563eb; }
}
@media (max-width: 600px) { padding: 8px; }
}迁移后,CI 里的样式编译步骤可以省掉一层。如果你用 GitHub Actions 自动化构建 做前端流水线,甚至可以把 Sass 编译 job 整个下线,缩短构建时间、减少依赖面。
九、渐进增强与回退(@supports)
需要兼容旧内核时,用 @supports 做特性检测:不支持嵌套的浏览器走平铺回退,支持的浏览器走嵌套写法。两条规则互斥,不会出现样式冲突。
示例 5:用 @supports 双写,平滑降级
/* 旧浏览器:平铺回退 */
@supports not (selector(&)) {
.card .title { font-weight: 600; }
.card .title:hover { color: #2563eb; }
}
/* 支持嵌套的浏览器 */
@supports (selector(&)) {
.card {
.title {
font-weight: 600;
&:hover { color: #2563eb; }
}
}
}另一个思路是在构建期用 Lightning CSS / PostCSS 自动把嵌套“展开”成平铺 CSS,这样源码写嵌套、产物兼容老浏览器。这特别适合暂时无法放弃旧版访问量的项目——既能享受开发期可读性,又不必手写两份样式。
十、常见坑与最佳实践
原生嵌套上手快,但也有几个高频踩坑点。下面这张清单建议在团队规范里直接固化:
| 易错点 | 正确做法 | 原因 |
|---|---|---|
| 嵌套层级过深(>3 层) | 控制在 2–3 层 | 过深难维护、特异性急剧膨胀 |
| 忘记 & 导致意外后代匹配 | 需要限定范围时显式写 & | .a { .b{} } 会匹配所有后代 .b |
| 滥用后缀 & | 一律用前缀 & | 后缀 & 兼容性晚、可读性差 |
| 特异性失控 | 用 :where() 降权 | 如 :where(&) 特异性为 0,便于覆盖 |
| 媒体查询散落 | 就近内嵌到组件 | 响应式逻辑与组件同框,最好维护 |
补充一个降权技巧:当你希望嵌套生成的样式容易被外部覆盖时,可以用 :where(&) 包裹,把整条规则的特异性降为 0。这在写可复用组件库时尤其有用,避免“高特异性战争”。
结语
原生 CSS 嵌套把“可读的就近书写”从预处理器带回了标准 CSS,且主流浏览器早已全面支持。对新项目,你可以大胆直接用;对老项目,用 @supports 或构建期降级也能平滑切入。配合 VS Code / JetBrains 提效技巧 里提到的编辑器 snippets,把常用嵌套骨架存成片段,落地效率会更高。下一次写样式时,不妨先问问自己:这一段,是不是可以不用 Sass 了?




