KAIROS.WORKSPACE
技术宇宙 / ONLINE
返回文章列表
前端工程intermediate8 分钟

CSS 自定义属性做主题系统:一套变量驱动全站换肤

用 RGB 三元组自定义属性构建分层主题变量,实现零重排的整站强调色切换,并把用户选择持久化到本地。

#CSS#设计系统#主题#可访问性
排版
字号
行宽
目录 · 6 节

本文为通用技术教程,示例均可在任意现代浏览器中复现。

目标:换的不只是颜色,是“角色”

主题系统常见的做法是给每个组件写一套 .theme-dark .card { ... } 覆盖。组件一多就会失控。更好的抽象是把颜色拆成角色(role)而不是位置

  • --accent-primary:主强调色(链接、主按钮、聚焦环)
  • --accent-primary-hover / --accent-primary-strong:交互态
  • --surface-* / --text-* / --border-*:中性层

组件永远只消费角色 token。换主题时,换的是角色的取值,而不是任何组件样式。

关键技巧:RGB 三元组 + alpha 组装

如果直接存 #38e8c6,透明度变体(hover 蒙层、发光、软背景)就没法复用。把颜色存成 R G B 三个数字,使用时再组装透明度:

:root {
  --accent-primary: 56 232 198;
  --accent-primary-soft: rgb(var(--accent-primary) / 0.12);
  --accent-primary-glow: rgb(var(--accent-primary) / 0.32);
}

.button {
  border-color: rgb(var(--accent-primary) / 0.55);
  box-shadow: 0 0 48px rgb(var(--accent-primary) / 0.09);
}

这样一套 token 就覆盖了实色、软背景、光晕、边框等所有透明度层级,主题切换只需要覆盖一个三元组。

换肤:属性覆盖 + 级联

html 元素上用 data-* 属性承载当前主题,CSS 里按属性选择器覆盖角色 token:

html[data-accent='coral'] {
  --accent-primary: 255 122 144;
  --accent-primary-hover: 255 158 174;
  --accent-primary-strong: 214 74 98;
}

注意两个细节:

  1. 只覆盖角色,不覆盖派生值--accent-primary-soft 这类派生 token 引用了 --accent-primary,自定义属性是运行时求值的,基础值变了派生值自动跟随——这正是分层 token 的意义。
  2. 选择器挂在 html 上而不是 :root 之外的元素,因为 :root 就是 html,属性选择器与它同级特异性更高,且随 DOM 属性切换即时生效。

JavaScript 侧只需一行:

document.documentElement.dataset.accent = 'coral';

没有重排、没有组件遍历、没有 CSSOM 注入。Canvas 里也能吃到同一套颜色:

const triplet = getComputedStyle(document.documentElement)
  .getPropertyValue('--accent-primary')
  .trim();
ctx.fillStyle = `rgb(${triplet} / 0.8)`;

首帧不闪烁

localStorage 恢复主题的脚本必须在首帧绘制之前执行,否则用户会看到一次默认色闪现。把内联脚本放在 <head> 顶部、任何样式表生效之前:

<head>
  <script is:inline>
    (() => {
      try {
        const { theme } = JSON.parse(
          localStorage.getItem('theme-prefs') || '{}',
        );
        if (['coral', 'cream', 'green'].includes(theme)) {
          document.documentElement.dataset.accent = theme;
        }
      } catch {
        /* 保持默认 */
      }
    })();
  </script>
  <!-- 样式表在之后加载 -->
</head>

使用 SPA 路由(或 Astro View Transitions)时还有一个隐藏利好:document.documentElement 跨导航持续存在,data-accent 属性不会因为换页丢失,无需在每个页面重复恢复。

无障碍校验

换肤改变了强调色,等于改变了对比度。上线前对每套主题过一遍:

  • 主色上的正文/按钮文字(浅色主配深字时确认对比度 ≥ 4.5:1)。
  • 彩色文字与其背景(提示:深底上的强调色文字看“亮度”而非“色相”)。
  • :focus-visible 轮廓在所有主题下清晰可辨。

四套主题值一起校验,比上线后再逐个修快得多。

小结

角色化 token + RGB 三元组 + 属性覆盖,三层叠加之后,“全站换肤”从一场组件大改造退化成三行 CSS。Canvas、Web Component、第三方样式都能通过同一套 token 对齐——这就是设计系统里“单一事实来源”的实际形状。

Conversation

评论与互动

正在加载评论…

提交后需审核,不会立即公开。

Keep exploring

继续探索