CSS 原生嵌套Nesting来了——这个无数次出现在开发者 wishlist 里的特性终于成了直接能在浏览器里跑的官方语法。一、从“复制粘贴父类”到原生嵌套1.1 那些年我们还在手动写父选择器在预处理器登上舞台前写原生 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 写法.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 一个最简例子.card{background:white;.title{font-size:20px;font-weight:bold;}}浏览器会自动转换为.card{background:white;}.card .title{font-size:20px;font-weight:bold;}写完这个你可能会说“这不就跟 SCSS 一模一样吗”——别急语法看上去相似但背后的规则跟 SCSS 比起来有微妙但重要的差异。2.2 符号父选择器的“定位器”Ampersand在嵌套中代表父选择器本身这在 SCSS 里玩得炉火纯青到了原生属性中依然保留了这一点。例如.button{background:navy;:hover{background:darkblue;}}浏览器会把它展开为.button{background:navy;}.button:hover{background:darkblue;}在多种场景下是必不可少的尤其是在这些场景中尤其重要伪类/伪元素:hover,::before组合选择器.active,:focus兄弟选择器 .sibling2.3 嵌套媒体查询让响应式逻辑归位原生嵌套支持把media查询直接放在规则内部让媒体查询的逻辑直接附着在你的组件上不必在文件底部到处翻了.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__titleCSS 变量动态✅ 原生支持运行时可变⚠️ 预处理静态变量编译后固定混入mixin❌ 无✅ 支持函数 逻辑if, each❌ 无✅ 支持样式复用extend❌ 无✅ 支持模块化use❌ 依赖import有已知性能问题✅ 现代模块化机制浏览器支持✅ 现代浏览器原生解析❌ 必须编译运行时主题切换✅通过 CSS 变量❌ 需重新编译这张表说明了一件事CSS 原生嵌套满足了 SCSS 80% 的日常场景但剩下那 20%——复杂的逻辑、循环、混入、模块化——SCSS 依然远远领先。3.2 BEM 拼接问题一个让老 SCSS 开发者“心碎”的差异如果你在 SCSS 中习惯了这种“优雅”写法.card { __title { /* .card__title */ } }你是完全没法在原生嵌套中原样照搬的。__title对浏览器来说不是一个有效的选择器原生 CSS 会直接忽略整条规则。你必须显式地写出完整的 BEM 类名.card{.card__title{……}}这对于习惯了用省字符的 SCSS 老用户来说可能得要一阵子来适应。3.3nest规则规范演进过程中的“历史遗存”早期的原生嵌套要求用nest把嵌套内容包装起来.card{background:white;nest .title{……}nest:hover{……}}如今的主流浏览器Chrome 120、Firefox 117、Safari 17.2已经不再强制要求nest这种写法在迁移旧项目时才会遇到。3.4 嵌套的选择器都会被 :is() 包裹重要浏览器对原生嵌套的底层实现是把嵌套的选择器放到:is()伪类里再处理.card{.title{……}}/* 实际上被解析为 */:is(.card) .title{……}看起来没啥但当你的父选择器是一个选择器列表时隐藏陷阱就来了.card, #featured-item{.title{……}}/* 解析为 */:is(.card, #featured-item) .title{……}嵌套出来的特异性会取:is()参数里最高的那个选择器特异性。在上面的例子里这里会拿到#featured-item的 ID 级权重。这意味着.title的样式突然变成了 ID 级别后面你可能用一个普通的类无法覆盖——它就彻底“锁死”了造成了过度特异性的麻烦。四、逐步迁移从 SCSS 到原生 CSS 可能保留的部分CSS 原生嵌套的出现并不意味着我们就要立刻全盘丢掉 SCSS而是帮你做一种渐进式、理性的取舍。4.1 迁移还是共存先回答三个问题在纠结“要不要换掉 SCSS”之前先问自己团队三条问题项目中是否大量使用了 SCSS 的mixin、extend、function、each等高级逻辑你们是否必须支持 IE 或非常老旧的内嵌浏览器如果使用 SCSS 编译成扁平 CSS 仍然让你感觉更安心运行时主题切换比如深色模式用户实时切换是否需要动态样式如果你的答案大多是“否”、“否”、“是”原生 CSS 做主、SCSS 做辅的共存策略会更稳妥。4.2 迁移示范手动重写 BEM 嵌套SCSS 中很常见的 BEM 缩写.card { __header { …… } __body { …… } __footer { …… } }迁移到原生 CSS你需要写具体类名.card{.card__header{……}.card__body{……}.card__footer{……}}重复写三次.card是有点啰嗦但我很严肃地告诉你这种啰嗦正是原生嵌套为了性能、可预测优先级做的最佳取舍。许多人最初会抱怨字符数增多但接受这种设计后反而会发现其长期维护成本更稳定。官方规范的动机正是避免选择器的过度特异性和构建出来完全出乎意料的规则组合。4.3 迁移示范嵌套媒体查询SCSS 写法.card { padding: 16px; media (min-width: 768px) { padding: 24px; } }原生 CSS 完全无缝兼容可以直接保留。.card{padding:16px;media(min-width:768px){padding:24px;}}media嵌套是 CSS Nesting 规范中最值得吹嘘的语法糖之一。4.4 构建工具配置让 PostCSS 处理你的原生嵌套就够了你依然可以保留构建工具但不再是“必须”。推荐配置安装postcss-nesting在postcss.config.js中启用将 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采用importlayer 原生 CSS 变量实现深浅色主题全量切换移除剩余的 SCSS 变量。结果主包 JavaScript 中移除sass依赖项与相关 loader 约 11MB 安装体积开发者再也不用在浏览器报错“找不到某个 SCSS 变量定义位置”了。六、那些年原生嵌套挖下的“坑”6.1 Tab 缩进造成的解析失败SCSS 中你不小心多缩进一行顶多是报错提示但原生 CSS 嵌套中如果一个标签选择器比如p、span没有用开头浏览器可能会把它当成“全局”选择器去匹配而不是你预想的嵌套后代关系。.card{span{/* 这会被解析为全局的 span而不是 .card span */}}更安全也推荐的做法是始终带上或者使用显式的后代选择器 span。6.2 浏览器版本被扫到时的策略截至 2026 年 5 月的 caniuseChrome / Edge 120、Firefox 117、Safari 17.2 均已稳定支持全量 CSS Nesting。为了做到渐进增强你可以在需要强调兼容性的项目开头用supports包裹一层检测supportsselector(){/* 放心大胆写你的嵌套样式 */}不支持的浏览器降级为扁平 CSS。6.3 超出三层的嵌套通通是“表演式编程”哪怕你的工具支持深嵌套也不代表你应该那样写。嵌套三层以上任何一种工具都会让你的代码难以维护同时让后续覆盖样式变成一个死局。一种靠谱的建议是“保持最多 2-3 层的嵌套剩余的依赖独立的类选择器管理。”/* 不建议嵌套过深 */.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 更现代的选择。八、终极思考当下与未来的原生 CSS8.1 现在你可以立刻停掉 SCSS 吗如果你是维护一个以信息展示为主的简要页面博客、营销官网。不用 SCSS 完全可行直接用原生 CSS。但如果面临以下场景我建议保留 SCSS但挪到“小范围辅助”的位置上复杂的数据表格、后台仪表盘样式大量依赖循环生成多个设计系统分支共享一套代码库8.2 推荐的工作流原生优先按需用 SCSS新组件使用.module.css CSS 原生嵌套跨组件样式复用、复杂逻辑、循环样式保留 SCSS 仅限于辅助作用全局样式变量采用原生 CSSvar(--xxx)深色模式切换完全依赖 CSS 运行时变量 JavaScript 类切换构建工具可选不必须如果为了 postcss 的自动前缀功能只需要保留这一层即可。结语六年之后回头来看 2019 年我曾许下的愿望——“希望浏览器能原生读懂嵌套”——终于成真。原生嵌套并不是为了直接杀死 SCSS而是要把 CSS 中最常用、日常高频的这一部分需求从预处理器中解放出来还给浏览器自己这样我们才能解放精力去解决更高层级的工程化问题。CSS 不会停下演化的脚步。嵌套只是一个开始未来还会有更多让人“原来它早已在那里”的伟大特性浮出水面。我唯一能给你个人的建议是多去实践保持对新标准的敏感度。在那些复杂的工程架构中“原生预处理器”可以共存但那份代码的简洁性、可维护性与可读性也许才是原生嵌套带来最可贵的东西。如 Chris Coyier 那句一直奉为圭臬的金句——“CSS 写到最后拼的都是维护它的最小代价。”