长久以来,Sass 和 Less 等预处理器的嵌套语法一直是前端开发者提升样式可读性的利器。现在,CSS 原生嵌套(CSS Nesting) 已正式成为浏览器标准,无需编译即可在原生样式表中使用。这一特性不是简单的语法糖,它从根本上改变了我们组织和维护 CSS 的方式。本文将深入解析原生嵌套的语法、规则与最佳实践,并通过三个完整的实战案例,让你立刻上手这项强大的新能力。
一、CSS 嵌套的核心语法
原生嵌套允许你将一个选择器放入另一个选择器内部,父选择器的上下文会被自动继承。基础形式与预处理器非常相似,但有一些独特的规则。
1.1 直接嵌套规则
最简单的方法是在父规则内部直接写子选择器。注意,嵌套的子规则必须以某个符号开头(如 &、.、#、[、: 等),不能直接以字母开头。这是为了与 CSS 属性声明区分。
/* 有效 */
.card {
background: white;
.title {
font-size: 1.2rem;
}
&:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
}
/* 无效:子选择器以字母开头会被误认为属性 */
.card {
span { /* 浏览器会将其视为属性 span,而不是选择器 */
color: red;
}
}
为了在嵌套中书写类型选择器(如 span、a),必须使用 & 符号作为前缀。例如:
.card {
& span {
font-weight: bold;
}
& a {
text-decoration: none;
}
}
1.2 & 符号的灵活运用
& 表示父选择器的完整引用,可用于生成复合选择器、反转上下文等。
.button {
background: blue;
/* &.primary 生成 .button.primary */
&.primary {
background: green;
}
/* &__label 生成 .button__label */
&__label {
font-size: 0.8rem;
}
/* 父选择器反转:html.dark & 生成 html.dark .button */
html.dark & {
background: darkblue;
}
}
1.3 媒体查询嵌套
可以将媒体查询直接嵌套在相关规则内部,避免重复编写选择器:
.card {
display: grid;
grid-template-columns: 1fr 1fr;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
这比传统写法更紧凑,所有与 .card 相关的响应式逻辑都集中在一起。
二、实战案例一:构建一个带暗黑模式的卡片组件
下面的例子展示了如何使用嵌套组织一个用户信息卡片的全部样式,包括子元素、伪类和暗黑模式适配。
/* 基础卡片样式 */
.user-card {
border-radius: 12px;
padding: 1.5rem;
background: var(--card-bg, #fff);
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
display: flex;
gap: 1rem;
align-items: center;
/* 头像 */
.avatar {
width: 60px;
height: 60px;
border-radius: 50%;
object-fit: cover;
border: 2px solid #e0e0e0;
}
/* 用户信息区域 */
.info {
flex: 1;
.name {
font-weight: 600;
font-size: 1.1rem;
margin: 0;
color: #333;
}
.bio {
font-size: 0.9rem;
color: #666;
margin-top: 0.25rem;
}
}
/* 悬停效果 */
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
transition: all 0.2s;
}
/* 操作按钮 */
.actions {
display: flex;
gap: 0.5rem;
button {
padding: 0.4rem 0.8rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 0.85rem;
&.primary {
background: #2b6ef0;
color: white;
}
&.secondary {
background: #f0f0f0;
color: #333;
}
&:hover {
opacity: 0.85;
}
}
}
/* 暗黑模式适配 */
@media (prefers-color-scheme: dark) {
background: #1e1e2e;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
.info {
.name { color: #eee; }
.bio { color: #aaa; }
}
.actions button.secondary {
background: #3a3a4a;
color: #ddd;
}
& .avatar {
border-color: #444;
}
}
}
整个卡片组件的所有样式都被限制在 .user-card 内部,命名冲突的风险大大降低。阅读代码时,组件结构一目了然。
三、实战案例二:响应式导航栏的嵌套写法
传统导航栏样式常常分散在不同位置。嵌套让我们能够按照 DOM 层级组织 CSS,同时将移动端适配逻辑直接内嵌。
/* 导航栏组件 */
.site-nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
background: white;
border-bottom: 1px solid #eaeaea;
position: sticky;
top: 0;
z-index: 100;
/* Logo */
.logo {
font-size: 1.5rem;
font-weight: 700;
color: #2b6ef0;
text-decoration: none;
&:hover {
opacity: 0.8;
}
}
/* 导航链接列表 */
.nav-list {
display: flex;
list-style: none;
gap: 2rem;
margin: 0;
padding: 0;
li a {
text-decoration: none;
color: #555;
font-weight: 500;
padding: 0.5rem 0;
border-bottom: 2px solid transparent;
transition: border-color 0.2s;
&:hover,
&.active {
border-bottom-color: #2b6ef0;
color: #2b6ef0;
}
}
}
/* 移动端菜单按钮(默认隐藏) */
.menu-toggle {
display: none;
background: none;
border: none;
font-size: 1.8rem;
cursor: pointer;
}
/* 移动端适配 */
@media (max-width: 768px) {
.menu-toggle {
display: block;
}
.nav-list {
display: none;
position: absolute;
top: 100%;
left: 0;
right: 0;
flex-direction: column;
background: white;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
padding: 1rem;
&.open {
display: flex;
}
}
}
}
通过嵌套,移动端和桌面端的样式天然组织在一起,避免了选择器的重复,并且所有与导航栏相关的规则都被封装在 .site-nav 命名空间内。
四、实战案例三:表单布局与错误状态的一体化样式
表单包含多种状态和子元素,非常考验样式的组织能力。利用嵌套,我们可以清晰地表达每个表单项的结构与交互状态。
/* 表单项组件 */
.form-group {
margin-bottom: 1.25rem;
display: flex;
flex-direction: column;
/* 标签 */
label {
font-size: 0.9rem;
font-weight: 500;
color: #444;
margin-bottom: 0.25rem;
& .required {
color: #e74c3c;
margin-left: 2px;
}
}
/* 输入框和文本域 */
input,
textarea,
select {
padding: 0.6rem 0.8rem;
border: 1px solid #ccc;
border-radius: 6px;
font-size: 1rem;
transition: border-color 0.2s, box-shadow 0.2s;
outline: none;
&:focus {
border-color: #2b6ef0;
box-shadow: 0 0 0 3px rgba(43, 110, 240, 0.15);
}
}
/* 错误状态 */
&.has-error {
input,
textarea,
select {
border-color: #e74c3c;
&:focus {
box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.15);
}
}
.error-message {
display: block;
}
}
/* 错误提示文字(默认隐藏) */
.error-message {
display: none;
color: #e74c3c;
font-size: 0.8rem;
margin-top: 0.25rem;
}
/* 帮助文本 */
.help-text {
font-size: 0.8rem;
color: #888;
margin-top: 0.25rem;
}
}
当需要在模板中为表单项动态添加 .has-error 类时,其内部输入框的边框和阴影会自动切换为错误样式,同时显示错误提示。无需再单独编写 .form-group.has-error input 这类长选择器。
五、注意事项与兼容性
- 特异性不变:CSS 嵌套在解析时会展开为等价的后代选择器,特异性与手写展开后的选择器相同,不会引入额外的权重。
- 避免过度嵌套:尽量不要超过三层,否则展开后的选择器会变得很长,影响渲染性能和可读性。这与预处理器的最佳实践一致。
- 浏览器支持:Chrome 120+、Edge 120+、Safari 17.2+、Firefox 117+ 均已支持 CSS 原生嵌套。对于需要兼容旧版本的项目,可以使用 PostCSS 插件将嵌套编译为传统选择器。
- 与预处理器的区别:原生嵌套与 Sass 的嵌套行为略有不同,例如
&的解析方式在复杂场景下可能有差异。建议在迁移时进行样式对比测试。
六、总结
CSS 原生嵌套的落地标志着样式编写进入了一个更模块化、更结构化的时代。它消除了对预处理器的硬性依赖,让开发者可以在纯 CSS 中享受到过去只有 Sass/Less 才能提供的便利。通过将样式逻辑封装在组件根选择器内,我们不仅减少了选择器重复,还显著提升了代码的可维护性。
从本文的三个实战案例可以看到,无论是卡片组件、响应式导航还是表单状态,嵌套都能让 CSS 与 HTML 结构更紧密地映射。如果你的项目尚未启用原生嵌套,现在就是在新模块中尝试它的最佳时机。在不久的将来,嵌套将成为每个 CSS 开发者自然而然的选择。

