跳转到内容

色彩

颜色分三层:基础色板(primitive,只声明一次的原始值)、语义角色(semantic,bg-* / fg-* / border-* / ring-*,皮肤只消费这一层)、组件覆盖槽--xh-<组件>-*)。这一页讲前两层怎么定,以及它们之间的对比度判据。产物与引入方式见设计令牌与主题

色彩模型

所有颜色写成 oklch(L C H),不用 hex / hsl。OKLCH 的明度 L 是感知均匀的:同一 L 的不同色相看起来一样亮,对比度只由 L 决定。于是整套色板可以只定一条明度曲线,换色相不改对比度;深色主题不是逐色手调,而是沿同一条曲线反向取档。

基础色板

十二个色相,每个色相 11 档(50 – 950)。每档的明度与彩度取品牌曲线的基线,彩度再按该档明度与色相收进 sRGB 色域。色相 258 那一族(indigo)与品牌色逐值相同——品牌就是色板里的一员。

red · 25
50oklch(0.971 0.014 25)
100oklch(0.936 0.032 25)
200oklch(0.885 0.062 25)
300oklch(0.809 0.105 25)
400oklch(0.702 0.165 25)
500oklch(0.623 0.214 25)
600oklch(0.546 0.216 25)
700oklch(0.488 0.196 25)
800oklch(0.424 0.164 25)
900oklch(0.379 0.132 25)
950oklch(0.282 0.089 25)
orange · 50
50oklch(0.971 0.014 50)
100oklch(0.936 0.032 50)
200oklch(0.885 0.062 50)
300oklch(0.809 0.105 50)
400oklch(0.702 0.165 50)
500oklch(0.623 0.214 50)
600oklch(0.546 0.216 50)
700oklch(0.488 0.196 50)
800oklch(0.424 0.164 50)
900oklch(0.379 0.132 50)
950oklch(0.282 0.089 50)
amber · 70
50oklch(0.971 0.014 70)
100oklch(0.936 0.032 70)
200oklch(0.885 0.062 70)
300oklch(0.809 0.105 70)
400oklch(0.702 0.165 70)
500oklch(0.623 0.18 70)
600oklch(0.546 0.183 70)
700oklch(0.488 0.196 70)
800oklch(0.424 0.164 70)
900oklch(0.379 0.132 70)
950oklch(0.282 0.089 70)
yellow · 95
50oklch(0.971 0.014 95)
100oklch(0.936 0.032 95)
200oklch(0.885 0.062 95)
300oklch(0.809 0.105 95)
400oklch(0.702 0.165 95)
500oklch(0.623 0.17 95)
600oklch(0.546 0.171 95)
700oklch(0.488 0.184 95)
800oklch(0.424 0.164 95)
900oklch(0.379 0.132 95)
950oklch(0.282 0.089 95)
lime · 125
50oklch(0.971 0.014 125)
100oklch(0.936 0.032 125)
200oklch(0.885 0.062 125)
300oklch(0.809 0.105 125)
400oklch(0.702 0.165 125)
500oklch(0.623 0.206 125)
600oklch(0.546 0.206 125)
700oklch(0.488 0.196 125)
800oklch(0.424 0.164 125)
900oklch(0.379 0.132 125)
950oklch(0.282 0.089 125)
green · 149
50oklch(0.971 0.014 149)
100oklch(0.936 0.032 149)
200oklch(0.885 0.062 149)
300oklch(0.809 0.105 149)
400oklch(0.702 0.165 149)
500oklch(0.623 0.214 149)
600oklch(0.546 0.205 149)
700oklch(0.488 0.196 149)
800oklch(0.424 0.164 149)
900oklch(0.379 0.132 149)
950oklch(0.282 0.089 149)
teal · 180
50oklch(0.971 0.014 180)
100oklch(0.936 0.032 180)
200oklch(0.885 0.062 180)
300oklch(0.809 0.105 180)
400oklch(0.702 0.147 180)
500oklch(0.623 0.138 180)
600oklch(0.546 0.131 180)
700oklch(0.488 0.13 180)
800oklch(0.424 0.132 180)
900oklch(0.379 0.132 180)
950oklch(0.282 0.089 180)
cyan · 215
50oklch(0.971 0.014 215)
100oklch(0.936 0.032 215)
200oklch(0.885 0.062 215)
300oklch(0.809 0.105 215)
400oklch(0.702 0.143 215)
500oklch(0.623 0.135 215)
600oklch(0.546 0.129 215)
700oklch(0.488 0.128 215)
800oklch(0.424 0.132 215)
900oklch(0.379 0.132 215)
950oklch(0.282 0.089 215)
blue · 237
50oklch(0.971 0.014 237)
100oklch(0.936 0.032 237)
200oklch(0.885 0.062 237)
300oklch(0.809 0.105 237)
400oklch(0.702 0.165 237)
500oklch(0.623 0.169 237)
600oklch(0.546 0.163 237)
700oklch(0.488 0.163 237)
800oklch(0.424 0.164 237)
900oklch(0.379 0.132 237)
950oklch(0.282 0.089 237)
indigo · 258(= brand)
50oklch(0.971 0.014 258)
100oklch(0.936 0.032 258)
200oklch(0.885 0.062 258)
300oklch(0.809 0.105 258)
400oklch(0.702 0.165 258)
500oklch(0.623 0.214 258)
600oklch(0.546 0.216 258)
700oklch(0.488 0.196 258)
800oklch(0.424 0.164 258)
900oklch(0.379 0.132 258)
950oklch(0.282 0.089 258)
purple · 302
50oklch(0.971 0.014 302)
100oklch(0.936 0.032 302)
200oklch(0.885 0.062 302)
300oklch(0.809 0.105 302)
400oklch(0.702 0.165 302)
500oklch(0.623 0.214 302)
600oklch(0.546 0.216 302)
700oklch(0.488 0.196 302)
800oklch(0.424 0.164 302)
900oklch(0.379 0.132 302)
950oklch(0.282 0.089 302)
pink · 345
50oklch(0.971 0.014 345)
100oklch(0.936 0.032 345)
200oklch(0.885 0.062 345)
300oklch(0.809 0.105 345)
400oklch(0.702 0.165 345)
500oklch(0.623 0.214 345)
600oklch(0.546 0.216 345)
700oklch(0.488 0.196 345)
800oklch(0.424 0.164 345)
900oklch(0.379 0.132 345)
950oklch(0.282 0.089 345)

怎么读这张色板:

  • 同一档跨色相同一明度。 600 档 L 0.546,任何色相的 600 档上铺白字都过 3:1(大字与图形档),700 档上铺白字都过 4.5:1;50 – 200 档上铺 --xh-fg-default 正文超过 9:1,100 档上铺同色相的 700 档文字过 4.5:1。给数据图、标签、头像按颜色点名时,换色相不必重算对比度。
  • 600 是锚点。 实心底取 600(配白字的正文档取 700),悬停 700、按下 800;淡底取 100 / 200,淡底上的文字取 700。
  • 黄、青这类天然明亮的色相中档偏沉。 这是"同档同明度"的代价:要一块亮黄,取 200 / 300 档,而不是把 600 调亮。
  • 皮肤不直接消费色板。 组件皮肤只认语义角色与语气轴;色板给使用者、数据可视化与自定义语气用。

色板由 packages/design/tokens/build/emit-palette.mjstokens/palette.seeds.json 的十二个色相角派生,改色相只改种子;tests/palette.spec.ts 逐档核对生成物、明度与运行时 deriveBrandScale 同源。

中性色板

十六档,含 450 / 550 / 650 / 750 四个半档:中性色承担文字、背景、边界三种角色,正文与次要文字、装饰边与控件边、面与抬起的面之间常常只差半档,整档跳会太跳。

0oklch(1 0 0)
50oklch(0.985 0 0)
100oklch(0.967 0.001 258)
200oklch(0.922 0.002 258)
300oklch(0.87 0.003 258)
400oklch(0.708 0.005 258)
450oklch(0.65 0.006 258)
500oklch(0.556 0.006 258)
550oklch(0.52 0.006 258)
600oklch(0.439 0.006 258)
650oklch(0.4 0.006 258)
700oklch(0.371 0.006 258)
750oklch(0.28 0.006 258)
800oklch(0.269 0.006 258)
900oklch(0.208 0.006 258)
950oklch(0.145 0.005 258)
浅色主题里的角色深色主题里的角色
0 / 50面(--xh-bg-surface)与页面底(--xh-bg-page反白前景
100 – 300淡底与三档边线(subtle 100 / default 200 / strong 300)
400 / 550 / 600禁用字 / 次要字 / 正文次级
700 / 800装饰边(default 700)与淡底
900 / 950正文(950)面(900)与页面底(950)

品牌色

品牌色不是一枚颜色,是一条从种子派生出来的 11 档梯度。派生只取种子的色相与彩度,明度曲线原样保留——语义层与语气层建立在明度之上的对比度保证,对任何种子都成立。

50oklch(0.971 0.014 258)
100oklch(0.936 0.032 258)
200oklch(0.885 0.062 258)
300oklch(0.809 0.105 258)
400oklch(0.702 0.165 258)
500oklch(0.623 0.214 258)
600oklch(0.546 0.216 258)
700oklch(0.488 0.196 258)
800oklch(0.424 0.164 258)
900oklch(0.379 0.132 258)
950oklch(0.282 0.089 258)
ts
import { registerBrand } from "@xihan-ui/tokens/runtime";

// 种子锚定在 600 档,即实心底与强调文字用的那一档
const dispose = registerBrand("ocean", "oklch(0.546 0.16 215)");
document.documentElement.dataset.brand = "ocean";

registerBrand[data-brand='ocean'] 取值块注进文档;不透明的任何 CSS 颜色都能当种子,落在 sRGB 色域外的彩度逐档收进来。切换品牌只换 data-brand,品牌淡底(12% / 20% / 28% 拼色)、焦点环、指示条全部跟着走。运行时 API 见 皮肤与样式分层 · 切换品牌色

功能色

四种语气各有一族原语,档位按"每一档要落在哪块面上"逐档验过对比度,不与基础色板共用曲线:黄族到 700 档也只有 3.75:1,够不着高对比档的判据,因此 warning 的 600 以上把色相从 86 转到 70。

语气原语与基础色板的关系典型用途
danger--xh-color-danger-400 … 700色相 25,同 red破坏性动作、校验失败、错误状态
success--xh-color-success-500 … 700色相 149,同 green完成、通过、在线
warning--xh-color-warning-400 … 800色相 86 → 70,介于 amber 与 yellow需要注意但不阻断
info--xh-color-info-500 … 700色相 237,同 blue中性提示、进行中
danger
400oklch(0.7 0.185 25)
500oklch(0.637 0.208 25)
600oklch(0.577 0.213 25)
700oklch(0.5 0.2 25)
success
500oklch(0.723 0.171 149)
600oklch(0.648 0.165 149)
700oklch(0.51 0.15 149)
warning
400oklch(0.86 0.14 86)
500oklch(0.795 0.161 86)
600oklch(0.705 0.16 70)
700oklch(0.62 0.15 70)
800oklch(0.55 0.14 70)
info
500oklch(0.685 0.156 237)
600oklch(0.62 0.16 237)
700oklch(0.53 0.15 237)

danger 动作与 error 状态分开定义,不共用业务语义;状态色表达任务结果,不表达空间层级。

语义角色

皮肤只消费这一层。每个实色底都配了前景(--xh-fg-on-brand--xh-tone-on),组件不自行计算文字色。取值随 data-theme 翻转,下面的色块画的是当前主题。

背景

令牌取值预览说明
--xh-bg-pagevar(--xh-color-neutral-50)页面底:面之下那一层,铺满视口
--xh-bg-canvasvar(--xh-color-neutral-0)不透明画布:自动填充遮罩、色块选中环等必须不透明的地方;控件盒静息不再填它
--xh-bg-surfacevar(--xh-color-neutral-0)静态内容面缺省底
--xh-bg-surface-raisedvar(--xh-color-neutral-0)抬起的面:Card、滑块
--xh-bg-subtlevar(--xh-color-neutral-100)淡底;白底上的 hover
--xh-bg-subtle-hovervar(--xh-color-neutral-200)白底上的 pressed;淡底上的 hover
--xh-bg-subtle-activevar(--xh-color-neutral-300)淡底上的 pressed,只留给按下
--xh-bg-mutedvar(--xh-color-neutral-100)禁用实心钮退到的中性面
--xh-bg-brandvar(--xh-color-brand-600)主要动作实心底
--xh-bg-brand-hovervar(--xh-color-brand-700)
--xh-bg-brand-activevar(--xh-color-brand-800)
--xh-bg-brand-subtlecolor-mix(in oklab, var(--xh-bg-brand) 12%, var(--xh-bg-surface))选中 / 当前专属,12% 品牌拼色
--xh-bg-brand-subtle-hovercolor-mix(in oklab, var(--xh-bg-brand) 20%, var(--xh-bg-surface))
--xh-bg-brand-subtle-activecolor-mix(in oklab, var(--xh-bg-brand) 28%, var(--xh-bg-surface))
--xh-bg-overlayoklch(0 0 0 / 0.45)模态遮罩

前景

令牌取值预览说明
--xh-fg-defaultvar(--xh-color-neutral-950)正文
--xh-fg-mutedvar(--xh-color-neutral-600)次要文字、说明
--xh-fg-subtlevar(--xh-color-neutral-550)占位、禁用标签
--xh-fg-disabledvar(--xh-color-neutral-400)禁用文字,对画布不低于 2.5:1
--xh-fg-on-brandvar(--xh-color-neutral-0)实心品牌底上的字
--xh-fg-on-brand-subtlecolor-mix(in oklab, var(--xh-bg-brand) 60%, var(--xh-fg-default))品牌淡底上的字
--xh-fg-brandvar(--xh-color-brand-600)普通底上的品牌文字与对号
--xh-fg-brand-strongvar(--xh-color-brand-700)
--xh-fg-successvar(--xh-color-success-700)
--xh-fg-warningvar(--xh-color-warning-700)
--xh-fg-infovar(--xh-color-info-700)
--xh-fg-dangervar(--xh-color-danger-600)

边界与焦点环

令牌取值预览说明
--xh-border-defaultvar(--xh-color-neutral-200)一切根面外边与 raised 面描边
--xh-border-subtlevar(--xh-color-neutral-100)只作内部分隔线
--xh-border-strongvar(--xh-color-neutral-300)只作高对比档与刻意登记的强调边
--xh-border-controlvar(--xh-border-default)控件边界,缺省档与 border-default 同色;高对比档才加深到 3:1
--xh-border-control-hovervar(--xh-color-neutral-400)控件悬停边
--xh-border-control-focusvar(--xh-ring-focus)聚焦边,与焦点环同色、不随语气
--xh-border-invalidvar(--xh-color-danger-600)
--xh-ring-focusvar(--xh-color-brand-600)公共键盘焦点环,对画布、面与淡底都 ≥ 3:1
--xh-ring-invalidvar(--xh-color-danger-500)

语气轴

data-tone 是六族语气的切换轴:brand、neutral、danger、warning、success、info。写在任何节点上,节点内即可取到整族 --xh-tone-*:实心底、实心底上的前景、淡底、淡底文字、描边、控件边界。组件按语气换色只经这条轴,不自行挑原语。取用方式与全表见 皮肤与样式分层 · 在自定义节点上使用语气

对比度判据

判据写在 packages/design/tokens/tests/contrast.spec.ts,改令牌先过它:

组合门槛
正文 / 次要文字对画布、面、淡底≥ 4.5:1(WCAG 1.4.3 AA)
焦点环对画布、面、品牌淡底≥ 3:1(WCAG 1.4.11)
语气实心底与淡底上的文字≥ 4.5:1,六族 × 明暗逐一核
控件边界 --xh-border-control缺省档与装饰边同色(1.26:1,设计决定);data-contrast="more" 下 ≥ 3:1;悬停档按棘轮不许更淡
装饰边 default / subtle / strong不在 1.4.11 范围内,但按棘轮钉住,不许悄悄变淡
禁用文字1.4.3 豁免,只钉住对画布不低于 2.5:1

控件边界这一条是刻意的取舍:输入框壳、勾选框、单选圈与旁边的浮层面板、卡片描边同一重量,页面里只有一种边线;缺省档靠占位文字、标签与聚焦环辨认控件,需要 3:1 边界的场景打开高对比档。

数据色板

热力图这类按颜色点名的组件走 data-palette 轴,六个色板各取一族的满档:green(success 600)、blue(info 600)、orange(warning 600)、purple(基础色板 purple 600)、red(danger 600)、gray(中性 600,深色档换 450)。色阶从 --xh-bg-subtle 到满档逐档明度严格单调,明暗两套都验过。更多颜色点名的场景直接取基础色板。

相关

Released under The MIT License