传统响应式布局最常用的是媒体查询:
@media (min-width: 768px) {
.card {
display: grid;
grid-template-columns: 160px 1fr;
}
}这类写法关心的是视口宽度。它适合页面级布局,但对组件来说经常不够精准。
同一个 ProductCard 可能出现在首页三栏网格里,也可能出现在侧栏推荐里。视口都是 1440px,但两个卡片实际拿到的宽度完全不同。如果只按视口判断,组件就很容易在某些容器里显得拥挤。
Container Queries 解决的就是这个问题:组件可以根据自己的容器宽度改变样式。
一句话定义
媒体查询问的是"浏览器窗口有多宽"。
Container Queries 问的是"我所在的容器有多宽"。
最小写法如下:
.card-shell {
container-type: inline-size;
}
@container (min-width: 420px) {
.product-card {
display: grid;
grid-template-columns: 140px 1fr;
}
}其中 .card-shell 是容器,.product-card 是容器里面被影响的元素。
inline-size 表示关注容器的行向尺寸。在常见横排页面里,你可以先简单理解为宽度。
为什么媒体查询不够
看一个常见页面:
<main class="layout">
<section class="content">
<ProductCard />
</section>
<aside class="sidebar">
<ProductCard />
</aside>
</main>在桌面端,主内容区可能有 760px,侧栏只有 280px。它们处在同一个视口里,但卡片需要的样式并不一样。
如果用媒体查询,你只能写:
@media (min-width: 1024px) {
.product-card {
display: grid;
grid-template-columns: 160px 1fr;
}
}问题是侧栏里的卡片也会变成横向布局,最后图片、标题、按钮挤在一起。
Container Queries 的思路更接近组件化:
.product-card-wrapper {
container-type: inline-size;
}
@container (min-width: 420px) {
.product-card {
display: grid;
grid-template-columns: 160px 1fr;
align-items: start;
}
}主内容区的卡片够宽,就切成横向布局。侧栏卡片不够宽,就继续保持纵向布局。
基础例子:自适应卡片
HTML 结构可以保持很普通:
<article class="product-card-wrapper">
<div class="product-card">
<img class="product-card__cover" src="/demo.jpg" alt="产品预览图">
<div class="product-card__body">
<h2>机械键盘入门指南</h2>
<p>从轴体、配列、键帽到连接方式,帮你避开第一把键盘的常见坑。</p>
<button>查看详情</button>
</div>
</div>
</article>默认样式先按窄容器设计:
.product-card-wrapper {
container-type: inline-size;
}
.product-card {
display: grid;
gap: 12px;
}
.product-card__cover {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
.product-card__body {
display: grid;
gap: 8px;
}当容器空间足够时,再增强成横向布局:
@container (min-width: 420px) {
.product-card {
grid-template-columns: 160px 1fr;
}
.product-card__cover {
height: 100%;
aspect-ratio: 1;
}
}这套写法的好处是:组件默认适配窄空间,只在空间足够时增加复杂布局。
给容器命名
页面复杂以后,可能会有多个容器嵌套。为了让规则更明确,可以给容器命名:
.profile-panel {
container-name: profile;
container-type: inline-size;
}
@container profile (min-width: 560px) {
.profile-card {
grid-template-columns: auto 1fr auto;
}
}也可以合写:
.profile-panel {
container: profile / inline-size;
}命名之后,读代码的人能更快知道这条规则依赖哪个容器。
如果组件会被放进很多不同区域,建议在组件外层建立自己的容器。这样组件样式不需要猜父级页面结构。
Container Queries 和 CSS Grid 很搭
Container Queries 适合处理组件内部布局,CSS Grid 适合处理二维结构。两者组合起来很自然。
例如一个统计面板:
<section class="stats-panel">
<div class="stats-grid">
<article>访问量</article>
<article>转化率</article>
<article>客单价</article>
<article>留存率</article>
</div>
</section>CSS:
.stats-panel {
container: stats / inline-size;
}
.stats-grid {
display: grid;
gap: 12px;
grid-template-columns: 1fr;
}
@container stats (min-width: 480px) {
.stats-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@container stats (min-width: 760px) {
.stats-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}这里的断点不再是整个页面的断点,而是统计面板自己的断点。
这让组件更容易复用。它被放进宽区域就展示四列,放进窄区域就自动回到一列或两列。
容器单位 cqw、cqh、cqi
Container Queries 还带来了一组容器查询单位。
常用的是:
cqw:容器宽度的 1%cqh:容器高度的 1%cqi:容器行向尺寸的 1%cqb:容器块向尺寸的 1%
例如标题字号可以跟随容器宽度轻微变化:
.hero-card {
container-type: inline-size;
}
.hero-card h2 {
font-size: clamp(20px, 6cqi, 36px);
}这比 vw 更适合组件。vw 依赖视口,组件放在侧栏里也会按整个窗口缩放,很容易过大。
不过要克制使用容器单位。字号、间距、图片尺寸可以适当响应容器,但不要让所有细节都动态变化,否则视觉会变得难以控制。
适合使用的场景
第一,复用型卡片。
商品卡、文章卡、用户卡、课程卡,经常出现在不同宽度的区域。它们非常适合根据容器切换布局。
第二,可拖拽或可配置面板。
仪表盘、低代码页面、后台工作台里,用户可能调整面板宽度。组件按自己的容器响应,比按视口响应自然得多。
第三,嵌入式模块。
同一个评论区、推荐列表、播放器控制条,可能被嵌入文章页、详情页、弹窗和侧栏。Container Queries 能减少页面层面的特殊覆盖。
第四,设计系统组件。
设计系统里的组件不应该强依赖某个页面断点。容器查询可以把响应式规则封装在组件内部。
不适合滥用的场景
页面整体布局仍然适合媒体查询。
比如顶部导航何时变成移动菜单、页面主栏和侧栏何时上下堆叠,这些通常是视口级决策。用媒体查询更直观。
不要为了使用 Container Queries,把每一层都设置成容器。容器会建立查询上下文,也可能影响某些布局计算。通常只需要给真正会影响内部布局的组件外层加容器。
也不要把业务状态塞进 CSS 断点。比如"会员用户展示更复杂布局",这是业务条件,不应该用容器宽度间接表达。
和 Tailwind 一起用
如果项目主要使用 Tailwind,可以把 Container Queries 作为组件 CSS 的补充。
一种务实做法是:布局基础类继续写在模板里,容器查询规则放到组件的 <style> 中。
<template>
<article class="article-card-wrapper">
<div class="article-card grid gap-3 rounded-lg border p-4">
<img class="cover w-full object-cover" src="/cover.jpg" alt="">
<div class="grid gap-2">
<h2 class="text-lg font-semibold">文章标题</h2>
<p class="text-sm text-slate-600">文章摘要...</p>
</div>
</div>
</article>
</template>
<style scoped>
.article-card-wrapper {
container: article-card / inline-size;
}
@container article-card (min-width: 520px) {
.article-card {
grid-template-columns: 180px 1fr;
}
.cover {
height: 100%;
}
}
</style>这样不会把复杂断点全部塞进 class 字符串里,组件的响应式意图也更清楚。
常见坑
第一,忘记设置容器。
只有写了 container-type 或 container 的元素,才会成为查询容器。否则 @container 找不到可用容器,规则不会按预期生效。
第二,查询的是祖先容器,不是自己。
@container 影响的是容器里面的后代元素,不是容器元素本身。如果你要改变卡片根元素的布局,通常需要在外面再包一层容器。
第三,断点太多。
组件断点应该服务于明显的布局变化。不要每隔几十像素就写一条规则,维护成本会很高。
第四,和页面媒体查询职责混乱。
页面结构用媒体查询,组件内部用容器查询。这个分工能避免样式规则互相覆盖。
总结
Container Queries 让响应式设计从"页面视口"走向"组件容器"。
它最适合解决这样的问题:同一个组件在不同区域被复用,但实际可用宽度不同。
使用时记住三条原则:
- 默认先写窄容器样式
- 空间足够时再用
@container增强 - 页面级布局继续交给媒体查询
这样写出来的组件更独立,也更符合现代前端的组件化思路。