你以为组件状态只能靠 JS 写一堆 class?今天 CSS 把这件事彻底原生化了

做过中后台系统的工程师大概都踩过这个坑——一个卡片组件有 default/primary/danger 三种状态,传统的做法是在 JS 里写 el.classList.toggle("card--" + variant),然后在 CSS 里写三段 .card--primary { ... } .card--danger { ... } 规则。每加一个状态,JS 要改、CSS 要改,两边的 class 名还要对得上。

CSS Style Queries 把这件事彻底变了——父容器上写一行 --variant: primary,子元素自己知道该用什么样式,JS 不用动一行。

原理

Style Queries 是 CSS Container Queries 的一部分,用 @container style() 查询祖先容器上的自定义属性值

/* 1. 父容器声明自己是查询容器 */
.card-wrap {
  container-name: card;
  container-type: inline-size;
}

/* 2. 用 @property 注册自定义属性(建议加类型注册) */
@property --variant {
  syntax: "<ident>";
  initial-value: default;
  inherits: true;
}

/* 3. 在父容器上写状态值 */
.card-wrap {
  --variant: primary;
}

/* 4. 子元素根据父容器属性自动响应 */
@container card style(--variant: primary) {
  .card {
    background: oklch(0.55 0.2 260);
    color: #fff;
  }
}

@container card style(--variant: danger) {
  .card {
    background: oklch(0.55 0.2 30);
    color: #fff;
  }
}

对比传统方案:

// 传统 JS 方案:每改一次状态要改两处
el.classList.remove("card--default", "card--primary", "card--danger");
el.classList.add("card--" + variant);

Style Queries 的本质是:把状态写在父容器上,样式向下级联自动响应。JS 只需要改父容器的一个属性值,不需要在每个子元素上做 class 操作。

三个常见坑

坑一:查询的目标不是自己

Style Query 查的是祖先容器,不是元素自己。在 .card-wrap 上查 --variant 会匹配 .card-wrap 上的值;但如果把 @container style() 写在 .card-wrap 自己身上,是不会生效的。规则只能查祖先,不能查自己。

坑二:@property 注册类型让查询更可靠

如果不注册 --variant 的类型,浏览器会把它当作无类型字符串。注册之后,Safari 和 Firefox 的行为会更一致:

@property --variant {
  syntax: "<ident>";
  initial-value: default;
  inherits: true;
}

没注册也能用,但注册之后 initial-value: default 确保未设置该属性的父容器也能匹配 style(--variant: default) 的兜底规则。

坑三:只能查自定义属性,不能查普通 CSS 属性

这是目前最大的限制——@container style(color: red) 在任何浏览器里都不生效,只能查 --custom-property: value。所以这套方案依赖组件提前把状态变量化,变成 --status --density --size 这类自定义属性。

Style Queries 完整三件套

Container Queries 其实有三类,Style Queries 是其中之一:

  • 尺寸查询 @container card (min-width: 400px) —— 查容器宽度,Baseline 2023
  • 样式查询 @container card style(--variant: primary) —— 查容器属性,Baseline 2026(新增)
  • 滚动状态查询 @container scroll-state(scrolled-y) —— 查滚动方向,Chrome 133+

三者可以叠加:@container card style(--variant: danger) and (min-width: 400px) 表示在 danger 状态且宽度大于 400px 时生效。

浏览器支持

Style Queries(仅自定义属性查询):Chrome 111+/Safari 18+/Firefox 151+。2026 年 5 月 Firefox 151 发布后,四大主流浏览器全部支持,Baseline 2026(新增可用),91.7% 全球覆盖率,已可投入生产。

滚动状态查询 @container scroll-state() 目前仅 Chrome 133+ 支持,需要渐进增强。

三步下一步

  1. 列清单:把组件里 JS 正在管理的 data-* 属性/状态变量列出来,把 data-variant--variantdata-status--status 映射好
  2. 注册类型:用 @property 注册这些状态属性,设定 initial-value 为默认值,inherits: true 确保嵌套组件也能读到
  3. 写查询规则:把原来 .btn--primary 里的样式迁移到 @container style(--variant: primary) 里,删掉 JS 里的 classList 操作

想消除 JS class 切换的工程团队,现在可以动手了。Style Queries 已在四大浏览器稳定支持,91.7% 全球覆盖,CSS 逻辑层正在把「状态」这件事从 JS 手里接过来。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 12 阅读