改个主题色要动几十个文件?今天 CSS 用两个新语法把设计系统彻底接管了

设计系统的工程师大概都踩过这个坑:主题变量定义了几十个,切换主题时要在每个用到的地方写一套 class 覆盖,或者写一段 JS 去批量计算。现在 CSS 用两个新语法把这件事彻底接上了——不需要 JS,不需要 Sass,只需要父容器声明一个变量,子组件自己会认主题。

第一个:CSS @function,浏览器原生的 Sass Mixin

以前写流体字体要这样:

/* Sass */
@function fluid($min, $max) {
  @return clamp($min, calc($min + ($max - $min) * ((100vw - 320px) / 960)), $max);
}

现在用 CSS 原生语法:

@function --fluid(--min, --max) {
  result: clamp(var(--min), 50vi, var(--max));
}

h1 { font-size: --fluid(1.5rem, 3rem); }
p  { font-size: --fluid(0.9rem, 1.1rem); }

@function 的核心能力:

  • 参数是 CSS 自定义属性(--min 这样写),不是 Sass 的 $variable
  • result: 描述符返回计算值,替代 Sass 的 @return
  • 在运行时使用 calc()vi/vb 视口单位等动态值——Sass 编译时拿不到这些
  • 可以组合调用:一个 function 里调用另一个 function

浏览器支持: Chrome/Edge 139+(~68% 全球覆盖),Firefox 和 Safari 尚未支持。用 @supports 做渐进增强:

h1 { font-size: clamp(1.5rem, 5vw, 3rem); } /* 回退 */

@supports (result: 0px) {
  h1 { font-size: --fluid(1.5rem, 3rem); }  /* 增强版 */
}

第二个:@container style(),组件自己读父容器变量

配合 @container style(),CSS 函数的能力才真正释放出来。

传统做法:切换深色主题要给每个组件加 data-theme="dark" class,然后:

[data-theme="dark"] .card { background: #1a1a1a; }
[data-theme="dark"] .btn  { border-color: #555; }
[data-theme="dark"] .badge { color: #fff; }
/* N 个组件,每个都要写一套覆盖规则 */

现在用 style query:

/* 父容器声明主题变量,一处声明 N 处响应 */
.theme-host { --theme: dark; }
.theme-host[data-tone="calm"] { --theme: calm; }

/* 子组件自己响应,不需要 class 覆盖 */
@container style(--theme: dark) {
  .card { background: #1a1a1a; }
  .btn  { border-color: #555; }
}

@container style(--theme: calm) {
  .card { background: #e8f4f8; }
  .btn  { border-color: #b8d4dc; }
}

改一行父容器的变量,N 个子组件自动响应。变量驱动样式,告别手动 class 覆盖。

配合 @property 注册变量类型还能更精确:

@property --theme {
  syntax: "<string>";
  initial-value: "light";
  inherits: true;
}

注册后 style() 里的值比较会按语义匹配,而不是按字符串 token,比较更可靠。

第三个:CSS if(),条件逻辑直接写进 CSS

Google I/O 2026 公布的 CSS if() 函数让 CSS 本身带上了条件逻辑:

--contrast: contrast-color(var(--card-bg));
color: if( style(--contrast: white): var(--text-dark); else: var(--text-light) );

结合自定义函数还能做更复杂的值映射:

@function --light-dark(--light-value, --dark-value) {
  result: if( style(color-scheme: dark): var(--dark-value); else: var(--light-value) );
}

background: --light-dark(#ffffff, #1a1a1a);

一个函数替代了以前需要 JS + CSS 变量联动才能实现的暗色模式切换。

设计系统落地路径

三个层次对应三种场景:

层次一:Token 层(现在就能用)
用 CSS 自定义属性定义设计 Token,@property 注册类型保证跨浏览器一致性。纯 CSS 工作,不需要任何构建工具。

层次二:Component 层(现在就能用)
在组件根元素声明 --theme--density 等变量,用 @container style() 让子组件自响应。相同 HTML 结构的组件在不同的父容器下自动适配,不需要两份 class 覆盖。

层次三:Logic 层(Chrome 139+ 增强)
@function 定义可复用的数学逻辑,用 if() 做条件判断。这层需要渐进增强,适合新项目直接用或旧项目选择性迁移。

下一步

动手迁移推荐按这个顺序:

  1. 先给组件根元素加 --theme 变量声明,@container style() 替换掉 data-theme 的 class 覆盖逻辑
  2. 把重复的 clamp() 计算抽成 @function,减少维护负担
  3. 在 Chrome 里用 DevTools 验证 @function 是否生效,Safari/Firefox 用 @supports 回退

浏览器支持仍是主要限制:@function 只有 68% 覆盖率,style query Safari 18+ 才完整支持。但用 @supports 包裹增强层,生产环境完全可用——回退到写死的 fallback 值,功能不降级。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 10 阅读