以前组件换肤要让父级配媒体查询,今天 CSS 自己会读自己的状态了——@container style() 把响应式从视口手里彻底抢回来了
我管组件的”变体”这件事,最早是用 CSS class 堆出来的。不同宽度配不同的 class,父级容器决定子组件长什么样。这个思路用了五六年,每次换设计稿都要改两处:父级媒体查询 + 子组件 class。
后来有了容器查询,组件终于不用知道自己在哪个父级容器里了——父级配 container-type: inline-size,子组件自己用 @container (min-width: ...) 查询尺寸。但这个还是有局限:只能查尺寸,不能查”状态”。
今天 CSS 终于把这个口堵上了——@container style() 允许组件直接查询自己的自定义属性值,组合 @property 注册类型后,连数值范围比较都能做。
@property 先把属性变成”可查的”
普通的 CSS 变量 var(--theme) 可以用,但无法被容器查询识别。需要先通过 @property 注册,告诉浏览器这个属性的类型:
@property --variant {
syntax: "<custom-ident>";
inherits: false;
initial-value: "default";
}
@property --level {
syntax: "<number>";
inherits: false;
initial-value: 1;
}
注册后的属性,会在 container-type: normal 的容器内建立”样式查询容器”上下文。注意:container-type: normal 不会建立尺寸查询容器,但可以建立样式查询容器。
@container style() 直接查自己
有了注册的 @property,组件可以直接查自己的状态,不需要父级参与:
.card {
container-type: normal;
container-name: card;
/* 组件默认状态 */
background: #f5f5f5;
padding: 1rem;
}
@container card style(--variant: featured) {
.card {
background: linear-gradient(135deg, #667eea, #764ba2);
padding: 2rem;
color: white;
}
}
@container card style(--variant: compact) {
.card {
padding: 0.5rem;
}
}
配合数值范围查询,还能做等级制样式响应:
@container card style(--level > 3) {
.card {
border: 2px solid gold;
box-shadow: 0 0 20px rgba(255, 215, 0, 0.3);
}
}
@container card style(--level > 7) {
.card {
border: 2px solid gold;
box-shadow: 0 0 30px rgba(255, 215, 0, 0.5);
animation: pulse 2s infinite;
}
}
这和以前有什么本质区别
以前响应式是”外部驱动”的:视口宽度或父级宽度变了,通知子组件重新渲染样式。这个通知链路要靠媒体查询或 JS 手动切换 class。
@container style() 把这个链路彻底倒过来了——组件自己定义状态变量,自己查询这个变量,状态变样式自动变。没有外部通知,没有 class 切换,没有 JS 什么事。
来看一个完整的实战例子,把变体和尺寸查询组合起来:
@property --card-variant {
syntax: "<custom-ident>";
inherits: false;
initial-value: "list";
}
@property --card-level {
syntax: "<number>";
inherits: false;
initial-value: 1;
}
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* 列表视图:紧凑横向 */
@container card style(--card-variant: list) {
.card { flex-direction: row; gap: 1rem; }
.card-image { width: 80px; height: 80px; }
}
/* 卡片视图:垂直堆叠 */
@container card style(--card-variant: card) {
.card { flex-direction: column; }
.card-image { width: 100%; aspect-ratio: 16/9; }
}
/* 等级决定边框粗细 */
@container card style(--card-level > 5) {
.card { border-width: 2px; }
}
@container card style(--card-level > 8) {
.card { border-width: 3px; border-color: gold; }
}
HTML 里只需要给容器配上 data 属性,CSS 自己会驱动整个变体系统:
<div class="card-wrapper" style="--card-variant: featured; --card-level: 7;">
<div class="card">...</div>
</div>
降级:没有 @property 怎么办
如果不用 @property 注册,直接在普通 CSS 变量上用 style(),浏览器会跳过这个查询,不报错也不生效。所以需要做两层降级:
.card {
container-type: normal;
/* 未注册时的默认值 */
background: #f5f5f5;
}
/* 已注册属性的增强样式 */
@property --card-variant {
syntax: "<custom-ident>";
inherits: false;
initial-value: "default";
}
@container card style(--card-variant: featured) {
.card { background: linear-gradient(135deg, #667eea, #764ba2); }
}
降级后的表现是组件退回到默认值,但不会坏。这个模式可以安全地逐步增强。
浏览器支持情况
@container style() 属于 Container Queries 规范,Chrome 106+ / Firefox 110+ / Safari 16+ 均已支持。@property 的支持范围更大,Chrome 85+ / Firefox 128+ / Safari 16.4+。
如果你的项目需要兼容更老的浏览器,可以用 @supports 做特性检测,或者在不支持的环境下回退到 JS 切换 class 的方案——但新项目完全可以默认开这个能力。
下一步怎么做
下次给组件设计变体系统时,先问自己一个问题:这个变体,真的需要父级来通知吗?
如果变体是由组件自身属性决定的(比如卡片等级、用户角色、数据状态),把 --variant 注册成 @property,组件自己 @container style() 查自己。如果变体是由容器尺寸决定的,继续用 @container (min-width: ...) 尺寸查询。
这两件事以前混在一起写,现在彻底拆开了。组件终于可以「自己管自己的状态」了。
评论区
登录后可评论。