跳到主要内容
我的站点
首页 归档 分类 标签 案例 服务 关于 聊聊需求

一套变量撑起明暗双主题:不用构建链的设计令牌

明暗主题最容易做成"两套颜色到处散落",改一处漏一处。用 CSS 自定义属性建立令牌层,一个 data-theme 属性切换,含对比度校验与渐变按钮的坑。

一套变量撑起明暗双主题:不用构建链的设计令牌 封面

#背景

暗色模式常见两种做法:写两套 CSS(.dark .card { ... }),或者用 CSS-in-JS 在运行时算颜色。前者随着组件变多会失控——同一个灰色出现在十几个文件里;后者需要构建链与运行时开销。

官网这类站点有更简单的解法:把颜色抽成令牌,一处在根上切换。零构建,纯原生。

#做法

#1. 令牌分三层

css
:root {
    /* 基础色板(只在这里出现真实色值) */
    --x-bg: #f7f8fb;
    --x-fg: #0b1220;
    --x-accent: #2563eb;        /* 科技蓝:白字压上去 5.2:1 */

    /* 派生态(由基础色板推导,组件只用这一层) */
    --x-accent-soft: rgba(37, 99, 235, 0.1);
    --x-line: #e4e8f0;
}

[data-theme='dark'] {
    --x-bg: #05070d;
    --x-fg: #eef3fb;
    --x-accent: #60a5fa;        /* 暗色下提亮,浅蓝底配深墨字 */
}

组件里**只允许出现 var(--x-*)**,不允许写 #2563eb。这条规则靠搜索就能守住:grep -rn "#[0-9a-f]\{6\}" public/assets/web/css/ 的结果里,只应剩 :root 与 [data-theme] 两处。

#2. 切换只有一个属性

js
document.documentElement.dataset.theme = 'dark'

配 color-scheme: dark 让浏览器原生控件(滚动条、表单)跟着变——少了这一步,暗色下会留一排刺眼的白色滚动条。

首次绘制要避免闪白:在 <head> 里同步读一次 localStorage 并写属性,不能等 DOMContentLoaded。

#3. 对比度必须量出来,不能凭感觉

配色最坑的地方是渐变按钮上的文字:浅蓝渐变 + 白字,对比度只有 2:1 左右,看着"很亮"其实读不清。做法是给按钮用实色底 + 深/浅字,并写个脚本量一遍:

bash
php vabora ui:contrast     # 明暗两套主题下逐项检查 WCAG AA

检查会覆盖正文、次要文字、按钮、标签、代码块等所有"文字压底色"的组合,低于 4.5:1(大字号 3:1)就报红。这个命令比"设计稿看着还行"可靠得多。

#4. 少量内联样式的取舍

品牌色渐变文字用 background-clip: text,渐变两端都要压得住底色:

css
.x-hero__title em {
    background: linear-gradient(104deg, var(--x-accent-deep), var(--x-accent));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

关键是两个端点都取深色档(--x-accent-deep → --x-accent),否则渐变末端会掉到 3:1 以下。

#结果

  • 明暗切换只改一个属性,无闪烁、无重排;
  • 全站 CSS 两个文件(约 76KB,gzip 18KB),没有构建步骤,改完刷新即刻生效;
  • 对比度由 ui:contrast 把关,改配色时不会悄悄引入不可读的组合。

#取舍

  • 不用 Sass/Tailwind:站点规模(几十个组件)用原生变量足够;引入构建链的收益是"少打字",成本是"多一层要维护的工具"。规模上去后可以再换,令牌层能原样迁移。
  • 不追动态取色:从封面图提取主色做主题很酷,但会让同一篇文章在两次访问里长得不一样,也难保证对比度。品牌色固定反而更稳。
最后更新: 本文采用 CC BY-NC-SA 4.0 许可,转载请注明出处与作者。
related

相关文章

评论

还没有评论,来说点什么吧。

发表评论

评论需站长审核后展示 · 最多 1000 字