写CSS的人很少有不依赖Sass或Less的。嵌套语法是其中最大的理由——它让层级关系清晰,避免了重复书写父选择器。但预处理器的嵌套最终会编译成一大堆平铺的选择器,有时还会产生不必要的长选择器链。2023年底,所有主流浏览器都已支持CSS原生嵌套(CSS Nesting),意味着你可以在没有构建工具的情况下,直接编写嵌套的CSS规则。
这不是简单的语法糖。原生嵌套有明确的解析规则,可以与:is()、&嵌套标识符配合,还能在媒体查询和容器查询内部嵌套规则。本文通过一个完整的卡片列表案例,把CSS嵌套的用法、陷阱和与Sass的差异讲清楚。
一、原生嵌套的基本语法
在原生CSS中,我们可以这样写嵌套:
.product-card {
background: white;
border-radius: 8px;
& .title {
font-size: 1.25rem;
font-weight: bold;
}
& img {
width: 100%;
display: block;
}
&:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
}
解析后等价于:
.product-card { background: white; border-radius: 8px; }
.product-card .title { font-size: 1.25rem; font-weight: bold; }
.product-card img { width: 100%; display: block; }
.product-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
规则很简单:一个选择器后面跟着{ },里面除了声明属性,还可以再写完整的选择器规则,这些规则必须以&开头(或者直接以元素标签、类名开头,但此时解析可能有歧义,最好始终使用&)。
注意:早期的原生嵌套草案允许直接写子选择器而不加&,但最终规范要求必须以&或元素选择器开头。推荐始终在嵌套选择器开头加上&,避免意外解析。
二、完整案例:一个带状态和响应式的卡片列表
假设我们有一个产品列表,每个卡片包含图片、标题、价格标签和收藏按钮。设计要求:卡片默认状态;悬停时放大并加深阴影;收藏按钮在悬停时变色;在暗色模式下调整背景和文字颜色;在小屏幕上卡片宽度变为100%。
2.1 HTML结构
<div class="product-list">
<div class="product-card">
<img src="product.jpg" alt="产品图">
<span class="badge">新品</span>
<h3 class="title">产品名称</h3>
<span class="price">¥199</span>
<button class="fav-btn">收藏</button>
</div>
<!-- 更多卡片 -->
</div>
2.2 用嵌套组织CSS
.product-list {
display: grid;
gap: 1.5rem;
padding: 1rem;
@media (width >= 768px) {
grid-template-columns: repeat(2, 1fr);
}
@media (width >= 1024px) {
grid-template-columns: repeat(3, 1fr);
}
}
.product-card {
background: var(--card-bg, #fff);
border-radius: 12px;
overflow: hidden;
transition: transform 0.2s, box-shadow 0.2s;
position: relative;
/* 卡片内部结构 */
& img {
width: 100%;
aspect-ratio: 4/3;
object-fit: cover;
}
& .badge {
position: absolute;
top: 8px;
left: 8px;
background: #ff4757;
color: #fff;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.75rem;
}
& .title {
font-size: 1.1rem;
margin: 0.75rem 1rem 0.25rem;
}
& .price {
color: #e63946;
font-weight: bold;
margin: 0 1rem;
}
& .fav-btn {
display: block;
margin: 0.75rem 1rem 1rem auto;
background: transparent;
border: 1px solid currentColor;
border-radius: 6px;
padding: 4px 12px;
cursor: pointer;
transition: background 0.2s, color 0.2s;
/* 收藏按钮悬停状态 */
&:hover {
background: #ff4757;
color: #fff;
border-color: #ff4757;
}
/* 已收藏状态(通过JS添加class) */
.product-card.favorited & {
background: #ff4757;
color: #fff;
border-color: #ff4757;
}
}
/* 卡片悬停效果 */
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(0,0,0,0.12);
}
/* 暗色模式适配 */
@media (prefers-color-scheme: dark) {
background: #2d2d2d;
color: #e0e0e0;
& .badge {
background: #ff6b81;
}
& .price {
color: #ff6b81;
}
}
}
这个样式表完整展示了嵌套的几种用法:
- 使用
&连接子元素(& img、& .title等) - 伪类嵌套(
&:hover) - 嵌套规则内再嵌套(
.fav-btn内部的&:hover) - 引用父选择器用于状态组合(
.product-card.favorited &) - 媒体查询嵌套在规则内部(
@media)
三、与Sass/SCSS的关键差异
- 字符串拼接:Sass中可以使用
#{$var}在选择器中插入变量,CSS原生嵌套不支持。如果需要在嵌套中使用变量,必须通过CSS自定义属性在属性值中使用,而不能在选择器字符串中拼接。 - 自动前缀:Sass可以输出带浏览器前缀的属性,CSS原生嵌套不具备这个能力,仍需依靠Autoprefixer。
- 父选择器引用:
&在Sass中代表完整的祖先选择器串,在CSS原生嵌套中也是,但CSS原生嵌套的解析规则更严格,&不能出现在选择器中间位置(如.card &__title这种BEM写法),只能出现在开头或结尾。 - 嵌套深度:CSS原生嵌套在浏览器中解析时,每一层嵌套都会产生一个
:is()包裹的选择器,深层嵌套可能导致特异度意外升高。建议嵌套深度不超过3层。
四、嵌套时需要注意的陷阱
第一个陷阱:无&开头时,浏览器会将嵌套规则解析为后代选择器还是新的全局选择器?早期实现有差异。现在的规范明确:如果一个嵌套规则以字母开头(即不以&、:、[等符号开头),则它被视为一个独立的选择器规则,不再继承父选择器。为了避免混淆,强烈建议任何嵌套规则都以&开头。
第二个陷阱:特异度。嵌套生成的:is(.parent .child)特异度等同于.parent .child,所以通常不会有意外。但如果嵌套内又使用了:is()或:where(),需要小心计算。
第三个陷阱:性能。极深的嵌套或复杂选择器在编译时(浏览器解析CSS时)会有少许额外开销,但实际项目中几乎感知不到。合理使用嵌套能减少样式表文件体积,反而有利于加载性能。
五、浏览器支持与迁移策略
所有主流浏览器(Chrome 120+、Edge 120+、Firefox 117+、Safari 17.2+)均已支持CSS原生嵌套。覆盖率在2024年末已超过92%。
如果你的项目已经在使用Sass,可以逐步尝试将部分组件的样式迁移到原生嵌套:
- 先从简单的、不依赖Sass变量和混入的组件开始。
- 保持Sass构建流程,但将一部分样式移到单独的
.css文件或<style>标签中,测试原生嵌套的兼容性。 - 如果项目完全不需要Sass的其他功能(变量、混入、循环),可以考虑去Sass化,仅使用PostCSS的Autoprefixer处理前缀。
对于还在使用旧浏览器的用户,嵌套会降级为无效样式,导致样式缺失。可以通过@supports或构建时回退来保证兼容性。例如:
/* 回退写法 */
.product-card & img { width: 100%; }
/* 嵌套写法(支持时才生效) */
@supports (selector(& img)) {
.product-card {
& img { width: 100%; }
}
}
但在实践中,如果用户浏览器市场份额已足够高,直接使用原生嵌套即可。
六、总结
CSS原生嵌套是前端工程化向前迈出的重要一步。它没有改变CSS的本质,只是让样式表的结构更贴近开发者组织代码的思维方式。通过本文的卡片列表案例可以看到,嵌套能让同一组件的样式内聚在一个代码块里,伪类、响应式、状态变化一目了然,不再需要反复回想.parent .child .grandson:hover .icon这种长选择器的具体含义。
从Sass迁移到原生嵌套不需要一蹴而就。你可以保留Sass作为构建工具,但同时体验原生嵌套带来的便利。等生态进一步成熟,或许有一天,CSS文件里就只剩下纯CSS了。

