原生嵌套(Nesting):以后还写 SCSS 吗?

CSS 原生嵌套(Nesting)来了------这个无数次出现在开发者 wishlist 里的特性,终于成了直接能在浏览器里跑的官方语法。

一、从"复制粘贴父类"到原生嵌套

1.1 那些年,我们还在手动写父选择器

在预处理器登上舞台前,写原生 CSS 是一个稍不留神就让人想摔键盘的过程。比如下面这个组件样式:

css 复制代码
.card {
  background: white;
  border-radius: 8px;
  padding: 16px;
}
.card__title {
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 8px;
}
.card__desc {
  font-size: 14px;
  color: #666;
}
.card .actions {
  margin-top: 16px;
}
.card .actions button {
  background: #4f46e5;
  color: white;
  border: none;
  border-radius: 4px;
  padding: 8px 12px;
}

是不是感觉身体被掏空?".card"反复出现,文件一大,想要改某个卡片组件的样式就只能在几百行 CSS 里反复搜索、滚动、修改。

1.2 SCSS 是如何"拯救"了我们的

SCSS(不含令人恐惧的缩进语法的 SASS 分支)用一套"嵌套"语法几乎一夜之间改变了 CSS 写法:

scss 复制代码
.card {
  background: white;
  border-radius: 8px;
  padding: 16px;

  &__title {
    font-size: 20px;
    font-weight: bold;
    margin-bottom: 8px;
  }

  &__desc {
    font-size: 14px;
    color: #666;
  }

  .actions {
    margin-top: 16px;

    button {
      background: #4f46e5;
      color: white;
      border: none;
      border-radius: 4px;
      padding: 8px 12px;
    }
  }
}

结构一下子就清晰起来了,代码量减少,组件的样式边界一目了然。正是嵌套让开发者第一次觉得"CSS 居然也可以有优雅的模块化"。

但这一切都依赖于一个编译环节:写的时候是 SCSS,最终落地浏览器时,还是得靠 Webpack/Vite/Gulp 等工具体系转译成原生 CSS 才能正常生效。直到今天,原生 CSS Nesting 终于填补了这块空白。


二、CSS 原生嵌套:浏览器终于懂你了

CSS 嵌套的核心思想与 SCSS 相似:将一个样式规则嵌套在另一个样式规则内部,浏览器会自动解析嵌套关系,让它变成原生 CSS 的选择器组合。

2.1 一个最简例子

css 复制代码
.card {
  background: white;

  .title {
    font-size: 20px;
    font-weight: bold;
  }
}

浏览器会自动转换为:

css 复制代码
.card {
  background: white;
}
.card .title {
  font-size: 20px;
  font-weight: bold;
}

写完这个,你可能会说:"这不就跟 SCSS 一模一样吗?"------别急,语法看上去相似,但背后的规则跟 SCSS 比起来,有微妙但重要的差异。

2.2 & 符号:父选择器的"定位器"

&(Ampersand)在嵌套中代表父选择器本身,这在 SCSS 里玩得炉火纯青,到了原生属性中依然保留了这一点。例如:

css 复制代码
.button {
  background: navy;

  &:hover {
    background: darkblue;
  }
}

浏览器会把它展开为:

css 复制代码
.button { background: navy; }
.button:hover { background: darkblue; }

& 在多种场景下是必不可少的,尤其是在这些场景中尤其重要:

  • 伪类/伪元素&:hover, &::before
  • 组合选择器&.active, &:focus
  • 兄弟选择器& + .sibling

2.3 嵌套媒体查询:让响应式逻辑归位

原生嵌套支持把 @media 查询直接放在规则内部,让媒体查询的逻辑直接附着在你的组件上,不必在文件底部到处翻了:

css 复制代码
.container {
  width: 100%;

  @media (min-width: 768px) {
    width: 80%;

    .inner {
      padding: 20px;
    }
  }
}

三、Native vs SCSS:差异比你想的大

下面这张表可能会在你考虑"我能不能直接替换 SCSS"时给你重要参考。

3.1 快速对照

特性 CSS 原生嵌套 SCSS
嵌套语法 ✅ 支持(类 SCSS 风格) ✅ 支持(更宽松灵活)
& 父选择器 ✅ 支持(但限制严格,不能做字符串拼接) ✅ 支持(完全拼接 + 组合)
BEM 拼接 .card__title 不能由 &__title 生成 &__title.card__title
CSS 变量(动态) ✅ 原生支持(运行时可变) ⚠️ 预处理静态变量(编译后固定)
混入(@mixin) ❌ 无 ✅ 支持
函数 & 逻辑(@if, @each) ❌ 无 ✅ 支持
样式复用(@extend) ❌ 无 ✅ 支持
模块化(@use) ❌ 依赖 @import(有已知性能问题) ✅ 现代模块化机制
浏览器支持 ✅ 现代浏览器原生解析 ❌ 必须编译
运行时主题切换 ✅(通过 CSS 变量) ❌ 需重新编译

这张表说明了一件事:CSS 原生嵌套满足了 SCSS 80% 的日常场景,但剩下那 20%------复杂的逻辑、循环、混入、模块化------SCSS 依然远远领先。

3.2 BEM 拼接问题:一个让老 SCSS 开发者"心碎"的差异

如果你在 SCSS 中习惯了这种"优雅"写法:

scss 复制代码
.card {
  &__title {
    /* .card__title */
  }
}

你是完全没法在原生嵌套中原样照搬的。&__title 对浏览器来说不是一个有效的选择器,原生 CSS 会直接忽略整条规则。

你必须显式地写出完整的 BEM 类名:

css 复制代码
.card {
  .card__title { ...... }
}

这对于习惯了用 & 省字符的 SCSS 老用户来说,可能得要一阵子来适应。

3.3@nest 规则:规范演进过程中的"历史遗存"

早期的原生嵌套要求用 @nest 把嵌套内容包装起来:

css 复制代码
.card {
  background: white;
  @nest & .title { ...... }
  @nest &:hover { ...... }
}

如今的主流浏览器(Chrome 120+、Firefox 117+、Safari 17.2+)已经不再强制要求 @nest,这种写法在迁移旧项目时才会遇到。

3.4 嵌套的选择器都会被 :is() 包裹(重要!)

浏览器对原生嵌套的底层实现是把嵌套的选择器放到 :is() 伪类里再处理:

css 复制代码
.card {
  .title { ...... }
}
/* 实际上被解析为 */
:is(.card) .title { ...... }

看起来没啥,但当你的父选择器是一个选择器列表时,隐藏陷阱就来了:

css 复制代码
.card, #featured-item {
  .title { ...... }
}
/* 解析为 */
:is(.card, #featured-item) .title { ...... }

嵌套出来的特异性,会取 :is() 参数里最高 的那个选择器特异性。在上面的例子里,这里会拿到 #featured-item 的 ID 级权重。这意味着,.title 的样式突然变成了 ID 级别,后面你可能用一个普通的类无法覆盖------它就彻底"锁死"了,造成了过度特异性的麻烦。


四、逐步迁移:从 SCSS 到原生 CSS(+ 可能保留的部分)

CSS 原生嵌套的出现,并不意味着我们就要立刻全盘丢掉 SCSS,而是帮你做一种渐进式、理性的取舍。

4.1 迁移还是共存?先回答三个问题

在纠结"要不要换掉 SCSS"之前,先问自己团队三条问题:

  1. 项目中是否大量使用了 SCSS 的 @mixin@extend@function@each 等高级逻辑?
  2. 你们是否必须支持 IE 或非常老旧的内嵌浏览器(如果使用 SCSS 编译成扁平 CSS 仍然让你感觉更安心)?
  3. 运行时主题切换(比如深色模式用户实时切换)是否需要动态样式?

如果你的答案大多是"否"、"否"、"是",原生 CSS 做主、SCSS 做辅的共存策略会更稳妥。

4.2 迁移示范:手动重写 BEM 嵌套

SCSS 中很常见的 BEM 缩写:

scss 复制代码
.card {
  &__header { ...... }
  &__body { ...... }
  &__footer { ...... }
}

迁移到原生 CSS,你需要写具体类名:

css 复制代码
.card {
  .card__header { ...... }
  .card__body { ...... }
  .card__footer { ...... }
}

重复写三次 .card 是有点啰嗦,但我很严肃地告诉你:这种啰嗦正是原生嵌套为了性能、可预测优先级做的最佳取舍 。许多人最初会抱怨字符数增多,但接受这种设计后反而会发现其长期维护成本更稳定。官方规范的动机正是避免选择器的过度特异性和构建出来完全出乎意料的规则组合

4.3 迁移示范:嵌套媒体查询

SCSS 写法:

scss 复制代码
.card {
  padding: 16px;

  @media (min-width: 768px) {
    padding: 24px;
  }
}

原生 CSS 完全无缝兼容,可以直接保留。

css 复制代码
.card {
  padding: 16px;

  @media (min-width: 768px) {
    padding: 24px;
  }
}

@media 嵌套是 CSS Nesting 规范中最值得吹嘘的语法糖之一。

4.4 构建工具配置:让 PostCSS 处理你的原生嵌套就够了

你依然可以保留构建工具,但不再是"必须"。推荐配置:

  1. 安装 postcss-nesting
  2. postcss.config.js 中启用
  3. 将 CSS 目标浏览器设置为 > 0.5%, last 2 versions, not dead

这样既支持了边缘旧浏览器,又使你大部分代码可以被那些"即将过气的旧浏览器"理解。


五、真实产品案例:渐进式迁移

某中型电商产品(Next.js + SCSS 前端)在 2025 年底启动样式系统重构。产品前端代码约有 12,000 行 SCSS 与 1800 行全局 CSS。团队决定分四个阶段迁移至原生 CSS(配合少量 SCSS):

  • 第一阶段(3天) :在全局根样式(global.css)去除大部分无用媒体查询,改用原生嵌套重写导航、页脚等低复用模块。
  • 第二阶段(10天) :改造 12+ 个可复用 UI 组件(Button, Card, Tag, Alert),删除所有 .scss 文件,全部迁移到带 .module.css 且搭配 CSS 变量的方式。组件数量减少 8%,但留下的组件更容易调试。
  • 第三阶段(2天):构建工具保留 PostCSS(用于 vendor prefix + 嵌套转译),删除 Dart Sass 编译层,构建时间减少约 40%(从平均 3.2s 降至 1.9s)。
  • 第四阶段(5天) :建立设计系统 token 文件(tokens.css),采用 @import + @layer + 原生 CSS 变量实现深浅色主题全量切换,移除剩余的 SCSS 变量。

结果:主包 JavaScript 中移除 sass 依赖项与相关 loader 约 11MB 安装体积;开发者再也不用在浏览器报错"找不到某个 SCSS 变量定义位置"了。


六、那些年原生嵌套挖下的"坑"

6.1 Tab 缩进造成的解析失败

SCSS 中你不小心多缩进一行,顶多是报错提示;但原生 CSS 嵌套中如果一个标签选择器(比如 pspan)没有用 & 开头,浏览器可能会把它当成"全局"选择器去匹配,而不是你预想的嵌套后代关系。

css 复制代码
.card {
  span { /* 这会被解析为全局的 span,而不是 .card span */ }
}

更安全(也推荐)的做法是始终带上 &,或者使用显式的后代选择器 & span

6.2 浏览器版本被扫到时的策略

截至 2026 年 5 月的 caniuse,Chrome / Edge 120+、Firefox 117+、Safari 17.2+ 均已稳定支持全量 CSS Nesting。为了做到渐进增强,你可以在需要强调兼容性的项目开头用 @supports 包裹一层检测:

css 复制代码
@supports selector(&) {
  /* 放心大胆写你的嵌套样式 */
}

不支持的浏览器降级为扁平 CSS。

6.3 超出三层的嵌套通通是"表演式编程"

哪怕你的工具支持深嵌套,也不代表你应该那样写。嵌套三层以上,任何一种工具都会让你的代码难以维护,同时让后续覆盖样式变成一个死局

一种靠谱的建议是:"保持最多 2-3 层的嵌套,剩余的依赖独立的类选择器管理。"

css 复制代码
/* 不建议(嵌套过深) */
.card {
  .content {
    .info {
      .title { ...... }
    }
  }
}

/* 建议 */
.card { ...... }
.card__content { ...... }
.card__info { ...... }
.card__title { ...... }

七、SCSS在现代2026开发中的定位

State of CSS 2026 报告有一个一针见血的辨析结论:对于大多数现代项目,SCSS 不再是"强标准"。之所以当今的现代 CSS 已逐步取代大部分预处理器中的"变量""嵌套"需求,根本原因是它们已是浏览器原生支持的规格。

但 SCSS 剩下的价值,在今天的几个关键领域里依然不可替代:

  • 大型组件库、设计系统(需要 @mixin@extend、循环生成样式)
  • 需要维护大量 预2023 年旧版 Legacy 项目的团队
  • 仅针对 CSS 生成做高级"逻辑运算"的场景(SCSS 里的函数循环配合颜色运算等)

不过,报告也明确指出了很多新项目的趋势:Tailwind + Vite + 原生 CSS 已成为比 SCSS 更现代的选择


八、终极思考:当下与未来的原生 CSS

8.1 现在你可以立刻停掉 SCSS 吗?

如果你是维护一个以信息展示为主的简要页面(博客、营销官网)。不用 SCSS 完全可行,直接用原生 CSS。

但如果面临以下场景,我建议保留 SCSS,但挪到"小范围辅助"的位置上:

  • 复杂的数据表格、后台仪表盘,样式大量依赖循环生成
  • 多个设计系统分支共享一套代码库

8.2 推荐的工作流:原生优先,按需用 SCSS

  1. 新组件:使用 .module.css + CSS 原生嵌套
  2. 跨组件样式复用、复杂逻辑、循环样式:保留 SCSS (仅限于辅助作用)
  3. 全局样式变量采用原生 CSS var(--xxx)
  4. 深色模式切换完全依赖 CSS 运行时变量 + JavaScript 类切换
  5. 构建工具可选(不必须),如果为了 postcss 的自动前缀功能,只需要保留这一层即可。

结语

六年之后,回头来看 2019 年我曾许下的愿望------"希望浏览器能原生读懂嵌套"------终于成真。

原生嵌套并不是为了直接杀死 SCSS,而是要把 CSS 中最常用、日常高频的这一部分需求,从预处理器中解放出来,还给浏览器自己,这样我们才能解放精力去解决更高层级的工程化问题。

CSS 不会停下演化的脚步。嵌套只是一个开始,未来还会有更多让人"原来它早已在那里"的伟大特性浮出水面。我唯一能给你个人的建议是:多去实践,保持对新标准的敏感度

在那些复杂的工程架构中"原生+预处理器"可以共存,但那份代码的简洁性、可维护性与可读性,也许才是原生嵌套带来最可贵的东西。如 Chris Coyier 那句一直奉为圭臬的金句------"CSS 写到最后,拼的都是维护它的最小代价。"

相关推荐
兄弟加油,别颓废了。1 小时前
系统全功能详细操作手册,从启动到测试
前端·chrome
ZC跨境爬虫2 小时前
跟着 MDN 学 HTML day_32:(AbstractRange 抽象接口与 DOM 范围操作)
前端·javascript·ui·html·音视频
十子木2 小时前
设置把所有终端移动到最前端的快捷键
前端
陈老老老板2 小时前
Bright Data Web Scraping 实战:用 MCP + Dify 构建 eBay 商品详情采集 AI 工作流(2026)
前端·人工智能
一渊之隔2 小时前
uniapp蓝牙搜索连接展示蓝牙设备包含信号显示
前端·网络·uni-app·bluetooth
Cisyam^2 小时前
Bright Data Web Scraper 实战:构建 TikTok 与 LinkedIn Web Scraping 自动化 Skill(2026)
运维·前端·自动化
李剑一2 小时前
开箱即用!Vue3+TS 视频组件完整代码,自动提取视频第一帧做封面。妈妈再也不用担心我手动截封面了
前端
盐多碧咸。。2 小时前
echarts折线图矩形选择 框选图表
前端·javascript·echarts
羽沢312 小时前
Canvas学习一
前端·css·学习·canvas