前言
你有没有过这种经历------写了一个看起来挺完美的组件,在大屏上美得像杂志排版,结果被塞进侧边栏里就变成了一个歪七扭八的怪物?
然后你开始加 @media (max-width: 768px),再加 @media (max-width: 480px),然后发现这个组件在主内容区 600px 宽的时候挺好,在侧边栏 300px 宽的时候又崩了。于是你开始写更细粒度的断点,断点越写越多,CSS 越来越乱,最后你看着那串 @media (min-width: 521px) and (max-width: 639px),心里只剩一个念头:
我到底在干嘛?
如果你有这种感觉,那恭喜你,你已经触碰到了传统响应式设计的核心缺陷------所有的 @media 都在盯着视口(viewport)看,而不是盯着组件自己的容器看。
而 CSS Container Queries,就是为了修复这个缺陷而生的。
一、痛点到底在哪?先说清楚
传统的 @media 查询,本质上是一种"近视眼"式的响应式。它只能看到浏览器窗口的大小,也就是视口宽度。但问题在于:一个组件该怎么展示,不应该取决于整个屏幕有多宽,而应该取决于它自己被放在了多大的空间里。
举个最直观的例子:
假设你有一个"用户卡片"组件,里面有头像、名字、简介、操作按钮。在大屏的主内容区,它有 800px 的空间,可以横向排列------头像在左,信息在右,按钮最右边,看起来很舒展。
但同一张卡片,如果被放在侧边栏,空间可能只有 250px。这时候它应该变成竖向排列------头像在上,信息在下,按钮居中。这是合理的布局变化。
用 @media 怎么做呢?你得这样:
css
/* 大屏时横排 */
.user-card {
display: flex;
flex-direction: row;
}
/* 视口小于 768px 时竖排 */
@media (max-width: 768px) {
.user-card {
flex-direction: column;
}
}
看起来没问题?问题大了。
如果大屏上侧边栏只有 250px 宽,视口是 1440px------你的 @media 根本不会触发,卡片在侧边栏里还是横排,溢出、挤压、丑得让人心痛。
更惨的是,同一个组件可能出现在页面的五六个不同位置------主内容区、侧边栏、弹窗、下拉菜单、底部推荐区......每个位置的容器宽度都不一样。你要为每个场景写不同的 @media?那你的 CSS 会变成一座断点的迷宫,没人能维护。
这就是 Container Queries 要解决的问题:让组件根据它自己所在容器的大小来决定样式,而不是根据整个视口。
二、Container Queries 的核心语法------比你想的简单
别被"容器查询"这个名字吓到,它的语法其实非常直觉。核心就两步:
第一步:声明容器
你需要告诉 CSS:"这个元素是一个容器,我要根据它的尺寸来做查询。"
arduino
.sidebar {
container-type: inline-size;
container-name: sidebar; /* 可选,给容器起个名字 */
}
container-type: inline-size 意味着我们关注的是容器的行内方向宽度(在英文环境下就是水平宽度)。这是最常用的类型。如果你需要同时查询宽和高,可以用 container-type: size,但目前浏览器对 size 的支持还不完整,inline-size 已经够解决绝大多数场景了。
你也可以用简写语法:
arduino
.sidebar {
container: sidebar / inline-size;
}
这一行同时定义了容器的名称(sidebar)和类型(inline-size),更简洁。
第二步:写容器查询
有了容器声明,你就可以在子元素上用 @container 来写查询了------语法跟 @media 几乎一模一样,只是把 media 换成了 container:
less
/* 容器宽度大于 400px 时,卡片横排 */
@container sidebar (min-width: 400px) {
.user-card {
flex-direction: row;
}
}
/* 容器宽度小于 400px 时,卡片竖排 */
@container sidebar (max-width: 399px) {
.user-card {
flex-direction: column;
}
}
如果你没有给容器命名(只用 container-type: inline-size),那就用匿名查询:
less
@container (min-width: 400px) {
.user-card {
flex-direction: row;
}
}
匿名查询会匹配最近的声明了 container-type 的祖先元素。
就这么简单。 你不需要 JavaScript,不需要 ResizeObserver,不需要任何库。纯 CSS,浏览器原生支持。
三、一个完整的实战案例------让组件真正"自适应"
我们来做一个更完整的例子,把整个流程串起来。
假设我们在做一个内容平台,有一个"文章推荐卡片"组件,它可能出现在以下位置:
- 主内容区旁边(宽度约 600px)
- 侧边栏(宽度约 280px)
- 底部推荐栏(宽度约 200px,三列并排)
同一个卡片,三个不同尺寸的容器,应该呈现三种不同的布局形态。
HTML 结构
xml
<main class="page-layout">
<div class="main-content">
<!-- 主内容文章 -->
<article>...</article>
</div>
<aside class="sidebar">
<div class="recommend-card">
<div class="card-body">
<h3 class="card-title">深度解析 TypeScript 5.0</h3>
<p class="card-desc">从装饰器到 const 类型参数,一次看懂所有重大更新...</p>
<div class="card-meta">
<span>张三</span>
<span>3.2k 阅读</span>
</div>
</div>
</div>
</aside>
<section class="bottom-recommend">
<div class="recommend-card">
<!-- 同样的卡片结构 -->
</div>
<!-- 更多卡片 -->
</section>
</main>
CSS:容器声明
arduino
.sidebar {
container: sidebar / inline-size;
width: 280px;
}
.main-content {
container: main / inline-size;
/* 主内容区宽度由布局决定,约 600px */
}
.bottom-recommend .recommend-card {
/* 底部卡片自身就是容器 */
container: bottom-card / inline-size;
width: 200px;
}
CSS:基础样式(最小容器下的竖排布局)
css
.recommend-card {
display: flex;
flex-direction: column;
background: #f8f9fa;
border-radius: 8px;
overflow: hidden;
}
.card-body {
padding: 12px;
}
.card-title {
font-size: 14px;
font-weight: 600;
line-height: 1.4;
}
.card-desc {
font-size: 12px;
color: #666;
margin-top: 4px;
display: none; /* 小容器下不显示描述 */
}
.card-meta {
font-size: 11px;
color: #999;
margin-top: 8px;
}
CSS:容器查询------不同容器宽度下的布局变化
less
/* 当容器宽度 >= 280px,卡片变成横向紧凑布局 */
@container sidebar (min-width: 280px) {
.recommend-card {
flex-direction: row;
align-items: center;
}
.card-body {
padding: 8px 12px;
}
.card-desc {
display: none; /* 280px 还不够显示描述 */
}
}
/* 当容器宽度 >= 400px,更舒展的横向布局 */
@container main (min-width: 400px) {
.recommend-card {
flex-direction: row;
}
.card-desc {
display: block; /* 空间够了,显示描述 */
}
.card-title {
font-size: 16px;
}
}
同一个 .recommend-card,同一个 CSS 文件,没有任何 JavaScript,它就能根据自己被放在了哪个容器里,自动选择最合适的布局。
如果你用 @media 来做同样的事情,你得为视口的每个断点写覆盖样式,还得处理"大屏但侧边栏窄"这种矛盾场景。用 Container Queries,每个容器自己管自己的子元素,逻辑清晰,互不干扰。
四、Container Queries vs @media------什么时候该用哪个?
很多人会问:那 @media 是不是要被淘汰了?
不是。它们解决的是不同层面的问题:
| 特性 | @media(视口查询) | @container(容器查询) |
|---|---|---|
| 查询对象 | 浏览器视口宽度 | 父容器宽度 |
| 适合场景 | 页面整体布局变化 | 组件内部布局变化 |
| 典型用途 | 大屏/小屏切换导航、侧边栏显隐 | 卡片、列表项在不同空间下的排列 |
| 作用层级 | 页面级 | 组件级 |
| 粒度 | 粗(整个屏幕只有一套断点) | 细(每个容器有自己的断点逻辑) |
最佳实践是:页面级布局用 @media,组件级适配用 @container。
比如:
- 导航栏在大屏横排、小屏折叠成汉堡菜单 →
@media - 侧边栏在小屏隐藏、大屏显示 →
@media - 一个卡片组件在宽容器横排、窄容器竖排 →
@container - 一个数据面板在大容器显示图表、小容器只显示数字 →
@container
把两者结合起来,你才能真正写出"无论放在哪里都能很好看"的响应式组件。
五、Container Query Units------容器查询的专用单位
除了 @container 规则,CSS 还配套了一组新的长度单位,叫 Container Query Length Units:
cqw:容器宽度的 1%cqh:容器高度的 1%cqi:容器行内尺寸的 1%cqb:容器块尺寸的 1%cqmin:cqi 和 cqb 中较小的那个的 1%cqmax:cqi 和 cqb 中较大的那个的 1%
用法跟 vw、vh 类似,但参照对象从视口变成了容器:
css
.card-title {
font-size: clamp(14px, 3cqw, 20px);
}
这段代码的意思是:标题字号最小 14px,最大 20px,中间值是容器宽度的 3%。容器越宽,字越大;容器越窄,字越小。clamp() 让它在两端都有保底,不会太极端。
这比用 @container 断点来手动设字号优雅得多------是一种"流式"的响应式,而不是"阶梯式"的。
实操建议: 对于字号、间距这种连续变化的属性,优先用 cqw + clamp();对于布局方向、显隐这种离散变化的属性,用 @container 断点。两者配合使用,效果最佳。
六、浏览器支持情况------现在能用在生产环境吗?
截至 2026 年年中,Container Queries 的支持情况如下:
- Chrome 105+(2022 年 8 月)------完整支持
- Safari 16+(2022 年 9 月)------完整支持
- Firefox 110+(2023 年 2 月)------完整支持
- Edge 105+------跟随 Chrome,完整支持
也就是说,主流浏览器的支持已经超过 3 年了,覆盖率超过 95%。现在完全可以放心用在生产环境。
唯一需要注意的是 container-type: size(同时查询宽和高)在部分浏览器中还有限制,但 inline-size(只查询宽度)已经完全稳定。绝大多数响应式场景只需要 inline-size,所以这不是问题。
七、常见坑和避坑指南
坑 1:忘了声明 container-type
容器查询不会自动生效,你必须显式地在父元素上声明 container-type。如果你写了 @container 规则但样式没生效,第一件事就是检查:你要查询的那个父元素,有没有声明 container-type: inline-size?
坑 2:查询了错误层级的容器
@container 会匹配最近的声明了 container-type 的祖先。如果你的组件嵌套了多层,每一层都有 container-type,查询可能匹配到你不想要的那一层。
解决方案:给容器命名。 用 container-name 或者简写 container: name / inline-size,然后在 @container name (min-width: ...) 里指定名字,就不会匹配错。
坑 3:在容器自身上写 @container
@container 查询的是父容器,不是元素自身。如果你在 .card 上同时声明了 container-type 和 @container 规则,那个 @container 会查询 .card 的父级容器,而不是 .card 本身。
如果你想让一个元素根据自身宽度变化样式,需要在它的子元素 上写 @container 规则,或者用 JavaScript 的 ResizeObserver 作为补充。
坑 4:cqw 单位在未声明 container 的祖先下失效
cqw 等容器单位只在有 container-type 声明的祖先范围内才有效。如果没有声明,它们会回退到视口单位(等同于 vw),这可能不是你想要的行为。确保使用容器单位时,对应的祖先已经声明了 container-type。
八、跟设计系统结合------Container Queries 最强大的用法
如果你在维护一套组件库或者设计系统,Container Queries 的价值会指数级上升。
为什么?因为设计系统的核心目标就是:组件应该是自包含的、可复用的、不依赖外部上下文的。
但传统的 @media 违背了这个原则------它让组件的样式取决于页面级的视口宽度,也就是外部上下文。这意味着同一个组件在不同页面、不同布局下可能表现不一致,而且组件本身无法控制这种行为。
有了 Container Queries,组件可以完全"自治":
css
/* 在组件自身的 CSS 文件中 */
.card-container {
container: card / inline-size;
}
@container card (min-width: 400px) {
.card-inner { flex-direction: row; }
}
@container card (max-width: 399px) {
.card-inner { flex-direction: column; }
}
这段 CSS 可以直接写在组件库的组件文件里,不需要任何外部断点配置,不需要知道页面布局是什么样的。 不管使用者把卡片放在哪里,它都能自动适配。
这才是真正的"可复用组件"------不是代码层面的复用,而是行为层面的自适应复用。
如果你是组件库的维护者,强烈建议现在就开始在每个布局组件中加入 container-type 声明和对应的 @container 规则。这是让你的组件库从"能用"升级到"好用"的关键一步。
九、一个你可能没想到的场景------弹窗和浮层里的响应式
Container Queries 还有一个特别实用的场景:弹窗、对话框、下拉菜单、tooltip 这些浮层组件。
这些组件有个共同特点:它们脱离了正常的页面布局流,尺寸由自身内容或 JavaScript 设定,跟视口宽度没有直接关系。
比如你有一个弹窗,宽度固定 480px。在这个弹窗里放了一个数据表格组件。用 @media,你无法单独控制这个表格在弹窗里的样式------因为视口可能 1440px,你的 @media (max-width: 768px) 根本不触发。
但用 Container Queries:
arduino
.dialog {
container: dialog / inline-size;
width: 480px;
}
@container dialog (max-width: 500px) {
.data-table {
font-size: 12px;
/* 简化表格布局 */
}
}
弹窗本身就是一个容器,里面的组件可以根据弹窗的尺寸来适配。不管视口多宽,弹窗里的东西永远是对弹窗负责,而不是对屏幕负责。
这种场景用传统 CSS 几乎无法优雅解决,而 Container Queries 一行代码就搞定。
总结
CSS Container Queries 不只是一个新语法,它是响应式设计思维的升级。
从"盯着屏幕"到"盯着自己的空间",这个转变听起来很小,但它从根本上解决了组件级响应式的痛点------那个困扰了我们十年的痛点。
现在浏览器支持已经足够成熟,语法足够简单,场景足够明确。如果你还没开始用,那 2026 年就是最好的时机。