CSS 嵌套实战:用原生嵌套语法重构你的样式表

还在为样式嵌套依赖 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 嵌套,无需开启任何实验标志。下面是关键版本的落地节点:

表格 1:主流浏览器对 CSS Nesting 的支持
浏览器最低支持版本状态
Chrome / Edge112✅ 默认支持
Safari16.5✅ 默认支持
Firefox117✅ 默认支持
iOS Safari16.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,这样源码写嵌套、产物兼容老浏览器。这特别适合暂时无法放弃旧版访问量的项目——既能享受开发期可读性,又不必手写两份样式。

十、常见坑与最佳实践

原生嵌套上手快,但也有几个高频踩坑点。下面这张清单建议在团队规范里直接固化:

表格 2:CSS 嵌套高频坑位与最佳实践
易错点正确做法原因
嵌套层级过深(>3 层)控制在 2–3 层过深难维护、特异性急剧膨胀
忘记 & 导致意外后代匹配需要限定范围时显式写 &.a { .b{} } 会匹配所有后代 .b
滥用后缀 &一律用前缀 &后缀 & 兼容性晚、可读性差
特异性失控用 :where() 降权如 :where(&) 特异性为 0,便于覆盖
媒体查询散落就近内嵌到组件响应式逻辑与组件同框,最好维护

补充一个降权技巧:当你希望嵌套生成的样式容易被外部覆盖时,可以用 :where(&) 包裹,把整条规则的特异性降为 0。这在写可复用组件库时尤其有用,避免“高特异性战争”。

结语

原生 CSS 嵌套把“可读的就近书写”从预处理器带回了标准 CSS,且主流浏览器早已全面支持。对新项目,你可以大胆直接用;对老项目,用 @supports 或构建期降级也能平滑切入。配合 VS Code / JetBrains 提效技巧 里提到的编辑器 snippets,把常用嵌套骨架存成片段,落地效率会更高。下一次写样式时,不妨先问问自己:这一段,是不是可以不用 Sass 了?

上一篇 Spring Boot Actuator 健康指标端点实战
下一篇 大模型输出护栏:用 Guardrails 做结构化校验与自愈
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]