← Back to Blog

CSS 在 2026 年终于”成年”了:原生嵌套、容器查询与 :has() 选择器实战指南

45 阅读 点赞
1| 1| 2| 2|

2026 年,CSS 迎来了一场静默却深刻的革命。曾经需要 Sass、Less 等预处理器才能实现的功能——嵌套语法、变量、混入——如今已被原生 CSS 逐一吸收。更令人兴奋的是,容器查询:has() 选择器的全面浏览器支持,让 CSS 第一次真正拥有了”组件级响应式”和”父选择器”能力。这意味着我们终于可以告别沉重的构建工具链,回归 CSS 的本来面貌。

3| 3| 4| 4| 5| 5| 6| 6|

本文将带你深入这三个改变游戏规则的特性,看看它们如何让前端开发变得更简洁、更强大,以及为什么 2026 年可能是预处理器时代走向终结的起点。

7| 7| 8| 8| 9| 9| 10|
现代CSS美学概念图
现代CSS美学概念:深空背景中漂浮的CSS规则块,磨砂玻璃质感与霓虹渐变光效
11| 12| 10| 13| 11| 14| 12|

一、原生嵌套:告别预处理器依赖的第一步

15| 13| 16| 14| 17| 15| 18| 16|

CSS Nesting Module 在 2023 年首次进入主流浏览器,到 2026 年已获得全面支持(含 Safari、Firefox 的完整兼容)。这意味着你不再需要 Sass 或 Less 就能在样式表中使用嵌套语法,显著减少了代码的层级重复。

19| 17| 20| 18| 21| 19| 22| 20|

原生嵌套的语法非常直观,与 Sass 高度相似,但有一些关键区别需要注意:

23| 21| 24| 22| 25| 23| 26| 24|
/* 原生 CSS 嵌套 */
    27|    25|.card {
    28|    26|  padding: 24px;
    29|    27|  border-radius: 12px;
    30|    28|  background: #fff;
    31|    29|
    32|    30|  & .title {
    33|    31|    font-size: 1.5rem;
    34|    32|    font-weight: 700;
    35|    33|    color: #1a1a2e;
    36|    34|  }
    37|    35|
    38|    36|  & .body {
    39|    37|    margin-top: 12px;
    40|    38|    line-height: 1.6;
    41|    39|
    42|    40|    & p {
    43|    41|      margin-bottom: 8px;
    44|    42|    }
    45|    43|  }
    46|    44|
    47|    45|  &:hover {
    48|    46|    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
    49|    47|  }
    50|    48|}
51| 49| 52| 50| 53| 51| 54| 52|

与 Sass 嵌套相比,原生 CSS 嵌套有一个重要区别:& 符号的使用更加严格。在原生语法中,& 必须放在选择器的开头位置(如 &:hover&.active),而 Sass 允许 & 出现在任意位置(如 .parent &)。这个限制看似是缺点,实际上鼓励了更清晰、更可维护的嵌套结构。

55| 53| 56| 54| 57| 55| 58| 56|

更值得注意的是性能层面。原生嵌套在浏览器引擎层面直接解析,不需要额外的编译步骤。这意味着开发时的样式更新可以即时生效,构建产物也更小——没有了预处理器生成的冗余选择器,CSS 文件体积平均减少 15%-20%。对于追求极致性能的团队来说,这是一个不可忽视的优势。

59| 57| 60| 58| 61| 59| 62| 60|

当然,原生 CSS 变量(Custom Properties)早已替代了 Sass 变量的角色,而 @layer 规则则提供了比 Sass 的 @import 更优雅的样式分层方案。当这些原生能力组合在一起时,预处理器的存在理由正在被一条条剥离。

63| 61| 64| 62| 65| 63| 66|
响应式容器查询概念可视化
容器查询概念:不同尺寸的半透明容器优雅排列,展示组件的自适应变化
67| 68| 64| 69| 65| 70| 66|

二、容器查询:响应式设计的范式转移

71| 67| 72| 68| 73| 69| 74| 70|

如果要用一句话概括容器查询的意义,那就是:响应式设计终于从”视口级”进化到了”组件级”

75| 71| 76| 72| 77| 73| 78| 74|

过去十年,我们用 @media 做响应式设计,判断的是浏览器视口的宽度。但问题在于,同一个组件可能出现在页面的不同位置——侧边栏里、主内容区、弹窗中——它们所处的容器宽度截然不同,却不得不共享同一套基于视口的响应式规则。这导致组件难以真正复用。

79| 75| 80| 76| 81| 77| 82| 78|

容器查询彻底改变了这一点。它允许你基于组件的父容器尺寸来调整样式,而不是整个视口:

83| 79| 84| 80| 85| 81| 86| 82|
/* 声明容器 */
    87|    83|.sidebar {
    88|    84|  container-type: inline-size;
    89|    85|  container-name: sidebar;
    90|    86|}
    91|    87|
    92|    88|/* 基于容器宽度响应 */
    93|    89|@container sidebar (min-width: 400px) {
    94|    90|  .product-card {
    95|    91|    display: flex;
    96|    92|    flex-direction: row;
    97|    93|    gap: 16px;
    98|    94|  }
    99|    95|
   100|    96|  .product-card .image {
   101|    97|    width: 200px;
   102|    98|    flex-shrink: 0;
   103|    99|  }
   104|   100|}
   105|   101|
   106|   102|/* 容器较窄时切换为纵向布局 */
   107|   103|@container sidebar (max-width: 399px) {
   108|   104|  .product-card {
   109|   105|    display: flex;
   110|   106|    flex-direction: column;
   111|   107|    gap: 8px;
   112|   108|  }
   113|   109|
   114|   110|  .product-card .image {
   115|   111|    width: 100%;
   116|   112|  }
   117|   113|}
118| 114| 119| 115| 120| 116| 121| 117|

这段代码的精妙之处在于:无论这个商品卡片被放到哪个容器中——侧边栏、主内容区、还是弹窗——它都能根据实际可用空间自动调整布局。你不再需要为每个使用场景写专门的样式覆盖,组件真正实现了”一次编写,到处适配”。

122| 118| 123| 119| 124| 120| 125| 121|

容器查询还催生了一个实用的配套属性:container-type: size。它允许子元素基于容器的尺寸使用百分比单位(包括高度),解决了长久以来”父元素高度依赖子元素内容、子元素百分比高度依赖父元素”的死循环问题。对于构建复杂的自适应布局,这是一个不可或缺的工具。

126| 122| 127| 123| 128| 124| 129| 125|

三、:has() 选择器:CSS 终于有了”关系选择器”

130| 126| 131| 127| 132| 128| 133| 129|

在 CSS 的漫长历史中,有一个问题困扰了开发者二十多年:如何根据子元素的状态来设置父元素的样式?由于 CSS 的渲染模型是从父到子单向传递的,”父选择器”一度被认为是不可能实现的特性。

134| 130| 135| 131| 136| 132| 137| 133|

:has() 选择器的出现打破了这个限制。它本质上是一个”关系选择器”,允许你基于元素内部或后续的子元素状态来匹配该元素本身:

138| 134| 139| 135| 140| 136| 141| 137|
/* 卡片中有图片时,增加内边距 */
   142|   138|.card:has(img) {
   143|   139|  padding: 0;
   144|   140|}
   145|   141|
   146|   142|.card:has(img) .content {
   147|   143|  padding: 16px;
   148|   144|}
   149|   145|
   150|   146|/* 表单字段有错误信息时,边框变红 */
   151|   147|.form-field:has(.error-message) {
   152|   148|  border-color: #e74c3c;
   153|   149|}
   154|   150|
   155|   151|.form-field:has(.error-message) label {
   156|   152|  color: #e74c3c;
   157|   153|}
   158|   154|
   159|   155|/* 导航项包含活跃子菜单时高亮 */
   160|   156|.nav-item:has(> .submenu .active) {
   161|   157|  background: var(--accent-color);
   162|   158|}
   163|   159|
   164|   160|/* 段落后面紧跟图片时,减少底部间距 */
   165|   161|p:has(+ img) {
   166|   162|  margin-bottom: 0;
   167|   163|}
168| 164| 169| 165| 170| 166| 171| 167|

这些示例展示了 :has() 的强大之处。过去要实现这些效果,你需要在 JavaScript 中手动检查 DOM 结构并添加/移除 class,既增加了运行时开销,也引入了额外的复杂度。现在,一切都在 CSS 层面声明式地完成。

172| 168| 173| 169| 174| 170| 175| 171|

关于性能,一个常见的担忧是::has() 会不会导致重绘性能问题?实际上,现代浏览器引擎(特别是 Chromium 113+ 和 Safari 17+)对 :has() 做了大量优化,在大多数实际场景中性能表现良好。但确实需要注意避免在深层嵌套结构中使用过于复杂的 :has() 链式选择器,比如 .container:has(.child:has(.grandchild:has(.active))) 这类写法可能会影响渲染性能。

176| 172| 177| 173| 178| 174| 179| 175|

最佳实践是将 :has() 用于结构性的条件样式(如”有图片就调整布局”、”有错误就变红”),而不是用于高频变化的交互状态。对于后者,传统的 class 切换方案仍然是更好的选择。

180| 176| 181| 177| 182| 178| 183|
CSS技术进化意境图
技术进化:由代码与光线构成的抽象植物,象征CSS技术的成熟与成长
184| 185| 179| 186| 180| 187| 181|

结语:回归原生的勇气

188| 182| 189| 183| 190| 184| 191| 185|

CSS 在 2026 年终于”成年”了。原生嵌套让我们告别预处理器的编译依赖,容器查询让组件级响应式成为现实,:has() 选择器补齐了 CSS 选择器体系的最后一块拼图。这三个特性合在一起,构成了一个完整的”后预处理器时代”技术栈。

192| 186| 193| 187| 194| 188| 195| 189|

当然,从”使用预处理器”到”回归原生”的迁移不会一蹴而就。Sass 在函数式编程能力(如循环、条件判断)上仍然有优势,大型项目中短期内仍会保留它。但对于新项目而言,直接使用原生 CSS 已经是完全可行的选择——更少的依赖、更快的构建、更清晰的调试体验。

196| 190| 197| 191| 198| 192| 199| 193|

技术的进步往往不是惊天动地的革命,而是一个个”终于不需要了”的瞬间叠加。当你发现某个工具的存在不再是必需,而只是习惯时,也许就是放手的时候了。

200| 194| 201| 195|

💬 发表评论