关于 color-palette
为设计与前端工作提供色彩系统生成指导,从起始色构建 OKLCH 色阶、语义令牌并检查对比度。适合需要将颜色应用于 CSS 或 Tailwind 主题的设计者与开发者。
主要功能
- 01按 OKLCH 生成 50–950 色阶
- 02定义品牌、语义与中性色令牌
- 03检查 WCAG 对比度
- 04提供暗色模式映射建议
- 05生成 Tailwind 配置与 CSS 变量
- 06指导色板视觉校验与设计上下文更新
适用场景
- 01从品牌色扩展完整色阶
- 02检查文本与背景配色是否达标
- 03为 CSS 或 Tailwind 创建颜色令牌
- 04为暗色主题映射语义颜色
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ arnavpuri/ designskills --skill color-palette安装后在 Claude Code 中输入/color-palette确认已加载。
附带文件scripts/palette.py
详细介绍
颜色调色板生成
从任意起点生成和谐、易于访问且可用于生产环境的颜色系统。
前置条件
生成颜色前,先检查是否已有设计上下文:
- 阅读
.agents/design-context.md(参见design-context技能)。如果其中包含颜色系统,则将这些十六进制值视为唯一可信来源,并在此基础上扩展,而不是替换。 - 如果代码库中已有设计系统(CSS 变量、Tailwind 主题、
tokens.json),则在此基础上扩展,而不是替换。
命名约定(与 design-system、layout-composition 等共用): 基础色阶为 --brand-50 … --brand-950、--gray-50 … --gray-950、--success-*、--warning-*、--error-*、--info-*;语义令牌为 --bg-*、--text-*、--border-*、--interactive-*,以及品牌别名 --color-primary、--color-secondary、--color-accent。
步骤 1:了解色彩理论基础
使用 OKLCH 生成调色板。其明度通道在感知上均匀,因此固定的 L 步长在每种色相下看起来都是相同的视觉变化(在 HSL 中,hsl(60 100% 50%) 黄色的亮度远高于“相同”亮度的 hsl(240 100% 50%) 蓝色)。仅在旧版工具强制要求时使用 HSL。请注意,高色度的 OKLCH 值可能超出 sRGB 色域;转换为十六进制时,应降低色度(保持 L 和 H 不变)。
色轮关系
| 和谐关系 | 与基色的角度 | 观感 |
|---|---|---|
| 互补色 | 180° | 高对比、充满活力 |
| 类似色 | ±30° | 和谐、自然 |
| 三角色 | 120°、240° | 鲜明、均衡 |
| 分裂互补色 | 150°、210° | 有对比但张力较弱 |
| 四角色 | 90°、180°、270° | 丰富,需要谨慎平衡 |
在 OKLCH 中生成和谐配色
:root {
/* Base brand color in oklch */
--brand-hue: 250; /* hue angle */
--brand-chroma: 0.15; /* chroma (not HSL saturation) */
--brand-l: 0.55; /* lightness */
--color-primary: oklch(var(--brand-l) var(--brand-chroma) var(--brand-hue));
--color-complement: oklch(var(--brand-l) var(--brand-chroma) calc(var(--brand-hue) + 180));
--color-analogous-1: oklch(var(--brand-l) var(--brand-chroma) calc(var(--brand-hue) + 30));
--color-analogous-2: oklch(var(--brand-l) var(--brand-chroma) calc(var(--brand-hue) - 30));
--color-triadic-1: oklch(var(--brand-l) var(--brand-chroma) calc(var(--brand-hue) + 120));
--color-triadic-2: oklch(var(--brand-l) var(--brand-chroma) calc(var(--brand-hue) + 240));
--color-split-comp-1: oklch(var(--brand-l) var(--brand-chroma) calc(var(--brand-hue) + 150));
--color-split-comp-2: oklch(var(--brand-l) var(--brand-chroma) calc(var(--brand-hue) + 210));
}
步骤 2:生成完整的色阶(50-950)
从单个品牌色生成 11 个明度级别(50、100-900、950)。在 OKLCH 中,改变明度,同时保持色相稳定,并在两端略微降低色度。
明度映射
| 令牌 | OKLCH 明度 | 用途 |
|---|---|---|
| 50 | 0.97 | 染色背景 |
| 100 | 0.93 | 悬停背景 |
| 200 | 0.87 | 激活背景 |
| 300 | 0.78 | 边框 |
| 400 | 0.68 | 占位文本 |
| 500 | 0.55 | 基础色/主要操作 |
| 600 | 0.48 | 主要操作悬停状态 |
| 700 | 0.40 | 主要操作激活状态 |
| 800 | 0.32 | 浅色背景上的标题 |
| 900 | 0.24 | 浅色背景上的正文 |
| 950 | 0.16 | 高对比度文本 |
CSS 实现
:root {
--brand-hue: 250;
--brand-chroma: 0.15;
--brand-50: oklch(0.97 calc(var(--brand-chroma) * 0.3) var(--brand-hue));
--brand-100: oklch(0.93 calc(var(--brand-chroma) * 0.4) var(--brand-hue));
--brand-200: oklch(0.87 calc(var(--brand-chroma) * 0.5) var(--brand-hue));
--brand-300: oklch(0.78 calc(var(--brand-chroma) * 0.7) var(--brand-hue));
--brand-400: oklch(0.68 calc(var(--brand-chroma) * 0.85) var(--brand-hue));
--brand-500: oklch(0.55 var(--brand-chroma) var(--brand-hue));
--brand-600: oklch(0.48 var(--brand-chroma) var(--brand-hue));
--brand-700: oklch(0.40 var(--brand-chroma) var(--brand-hue));
--brand-800: oklch(0.32 calc(var(--brand-chroma) * 0.9) var(--brand-hue));
--brand-900: oklch(0.24 calc(var(--brand-chroma) * 0.8) var(--brand-hue));
--brand-950: oklch(0.16 calc(var(--brand-chroma) * 0.7) var(--brand-hue));
}
步骤 3:定义语义颜色
将用途明确的令牌映射到调色板,或定义独立色相。
推荐的语义色相
| 角色 | 色相(OKLCH) | 原因 |
|---|---|---|
| 成功 | 145 | 绿色,普遍代表“继续” |
| 警告 | 85 | 琥珀色/黄色,表示谨慎 |
| 错误 | 25 | 红色,表示紧急 |
| 信息 | 230 | 蓝色,中性信息 |
:root {
/* Semantic: success */
--success-50: oklch(0.97 0.04 145);
--success-400: oklch(0.68 0.15 145); /* dark-mode foreground */
--success-500: oklch(0.55 0.16 145);
--success-700: oklch(0.40 0.14 145);
/* Semantic: warning */
--warning-50: oklch(0.97 0.04 85);
--warning-400: oklch(0.72 0.15 85);
--warning-500: oklch(0.60 0.16 85); /* ~4:1 on white: use dark text ON it, 700 for warning TEXT */
--warning-700: oklch(0.45 0.14 85);
/* Semantic: error */
--error-50: oklch(0.97 0.04 25);
--error-400: oklch(0.68 0.16 25);
--error-500: oklch(0.55 0.18 25);
--error-700: oklch(0.40 0.16 25);
/* Semantic: info */
--info-50: oklch(0.97 0.04 230);
--info-400: oklch(0.68 0.12 230);
--info-500: oklch(0.55 0.14 230);
--info-700: oklch(0.40 0.12 230);
}
只有当产品需要时(警报、徽章、图表),才为语义色相生成完整的 50-950 色阶;50/400/500/700 已足以覆盖大多数 UI。
步骤 4:生成中性色阶
中性色是基础。添加少许来自品牌色的色相倾向,营造暖感。
:root {
--neutral-hue: var(--brand-hue);
--neutral-chroma: 0.01; /* barely tinted */
--gray-50: oklch(0.98 var(--neutral-chroma) var(--neutral-hue));
--gray-100: oklch(0.96 var(--neutral-chroma) var(--neutral-hue));
--gray-200: oklch(0.90 var(--neutral-chroma) var(--neutral-hue));
--gray-300: oklch(0.83 var(--neutral-chroma) var(--neutral-hue));
--gray-400: oklch(0.71 var(--neutral-chroma) var(--neutral-hue));
--gray-500: oklch(0.55 var(--neutral-chroma) var(--neutral-hue));
--gray-600: oklch(0.45 var(--neutral-chroma) var(--neutral-hue));
--gray-700: oklch(0.37 var(--neutral-chroma) var(--neutral-hue));
--gray-800: oklch(0.27 var(--neutral-chroma) var(--neutral-hue));
--gray-900: oklch(0.20 var(--neutral-chroma) var(--neutral-hue));
--gray-950: oklch(0.13 var(--neutral-chroma) var(--neutral-hue));
}
步骤 5:确保符合 WCAG 无障碍标准
对比度要求(WCAG 2.2——当前法律/行业标准)
| 级别 | 正文 | 大号文本(≥24px,或 ≥18.66px / 14pt 粗体) | 非文本 UI(图标、边框、焦点环、图表标记) |
|---|---|---|---|
| WCAG AA | 4.5:1 | 3:1 | 与相邻颜色的对比度为 3:1(SC 1.4.11) |
| WCAG AAA | 7:1 | 4.5:1 | -- |
对比度 = (L1 + 0.05) / (L2 + 0.05),其中 L1/L2 是较亮/较暗的相对亮度(在线性化 sRGB 中为 0.2126 R + 0.7152 G + 0.0722 B)。对比度不会向上取整:4.49:1 不合格。APCA(Lc 值)是 WCAG 3 的候选草案,可作为第二意见,尤其适用于深色模式,但绝不能将其报告为合规标准。
实用规则
- 浅色背景上的正文:使用 900 或 950 色阶(按上述亮度映射,可轻松通过 AA)。
- 深色背景上的正文:使用 50 或 100 色阶。
- 主按钮:白色文字配 500+ 色阶通常可以通过(L 0.55 的蓝色 ≈ 4.9:1),但亮度为 L 0.55-0.65 的黄色/琥珀色/青色通常不合格——请改用深色文字。务必测量。
- 禁用状态:浅色背景上使用 400 色阶。禁用元素虽不受 WCAG 约束,但仍应易于区分。
- 链接:必须通过颜色之外的方式与周围文本区分——为链接添加下划线,或确保其与周围文本的对比度达到 3:1,并在悬停/聚焦时提供非颜色提示。
使用 OKLCH 亮度快速检查对比度
对于近中性色,相对亮度 ≈ L³,因此在亮度极端值下,4.5:1 需要约 0.42-0.45+ 的亮度差(中间范围则需要更大差值)。仅用它筛选候选颜色,然后再测量。
text L 0.20 | bg L 0.97 | diff 0.77 -> ~16.6:1, passes AAA
text L 0.55 | bg L 0.97 | diff 0.42 -> ~4.5:1, borderline AA -- measure; safe for large text
text L 0.57 | bg L 0.97 | diff 0.40 -> ~4.1:1, FAILS AA for normal text
测量,不要猜测
浏览器可直接渲染 OKLCH,但设计上下文中存储的是 hex 值,对比度必须按 sRGB 计算。使用随附的脚本(<color-palette> 和 <design-context> 是这两个技能的目录):
python <color-palette>/scripts/palette.py --from "#2563eb" # scale around a brand color
python <color-palette>/scripts/palette.py 250 0.15 --name brand --css # hue + chroma -> CSS vars
python <color-palette>/scripts/palette.py 250 0.02 --name gray --json # neutrals as JSON
python <design-context>/scripts/contrast.py "#ffffff" "#2563eb" # any real pairing
palette.py 会输出每个色阶的 hex 值,以及与白色/黑色的对比度,并标记为 AA(4.5:1)、large/UI(3:1)或 -。超出色域的色阶会降低色度,而不会改变亮度或色相。contrast.py 在检查不通过时会以 1 退出,并建议最接近的合格色阶。
还要检查你实际使用的每一组文本/背景搭配(文本配 --bg-*、按钮标签配 --interactive-*、深色模式配色),而不仅仅是与纯白/纯黑的对比度。
步骤 6:适配深色模式
不要简单地反转色阶,而应重新映射语义用途。
深色模式策略
[data-theme="dark"] {
/* Backgrounds: use 900/950 instead of 50/white */
--bg-primary: var(--gray-950);
--bg-secondary: var(--gray-900);
--bg-tertiary: var(--gray-800);
/* Text: use 50/100 instead of 900/950 */
--text-primary: var(--gray-50);
--text-secondary: var(--gray-300);
--text-tertiary: var(--gray-400);
/* Surfaces: slightly lighter than bg */
--surface: var(--gray-900);
--surface-hover: var(--gray-800);
/* Borders: use 700 instead of 200 */
--border-default: var(--gray-700);
/* Brand colors: increase lightness for dark bg */
--interactive-primary: var(--brand-400); /* lighter than 500 */
/* Semantic: bump to lighter shades */
--color-success: var(--success-400);
--color-warning: var(--warning-400);
--color-error: var(--error-400);
}
这些名称必须与步骤 10 中浅色模式第 2 层令牌一致——深色模式只重新映射它们。如需完整的深色主题(高程表面、图像、切换控件),请交由 dark-mode 技能处理。
关键原则
- 略微降低对比度——纯白配纯黑会过于刺眼。使用 gray-50 配 gray-950。
- 在深色模式下略微降低色度——高饱和度颜色在深色背景上会发光。
- 深色模式中的高程使用更亮的表面(与浅色模式的阴影相反)。
步骤 7:按行业选择颜色心理学
| 行业 | 主色 | 原因 |
|---|---|---|
| 金融 | 海军蓝、深蓝、绿色 | 信任、稳定、增长 |
| 医疗 | 蓝色、青绿色、白色 | 平静、洁净、信任 |
| 食品 | 红色、橙色、黄色 | 食欲、活力、温暖 |
| 科技/SaaS | 蓝色、紫罗兰色、青色 | 创新、可靠 |
| 奢侈品 | 黑色、金色、深紫色 | 优雅、独特 |
| 环保/绿色 | 绿色、大地色系 | 自然、可持续发展 |
| 教育 | 蓝色、橙色、绿色 | 信任、热情、增长 |
| 创意 | 鲜明的三原色、霓虹色 | 活力、表达、乐趣 |
步骤 8:渐变组合
有效渐变的规则
- 柔和渐变的色相旋转角度应控制在 60 度以内。
- 鲜艳、充满活力的渐变可使用 90–180 度。
- 在
oklch()中书写色标并不会让渐变以 OKLCH 进行插值。没有插值提示时,浏览器会对现代颜色使用 Oklab,对 hex/rgb/hsl 色标使用 sRGB——这两种方式都会直接穿过色彩空间,在相距较远的色相之间可能经过灰色。添加in oklch,即可沿色相环进行插值(彩虹渐变可添加longer hue)。 - 或者添加一个中间色标,并稍微提高其色度,以避开“灰色死区”。
/* Subtle brand gradient */
.gradient-subtle {
background: linear-gradient(
135deg in oklch,
oklch(0.55 0.15 250),
oklch(0.55 0.15 280)
);
}
/* Vibrant hero gradient */
.gradient-vibrant {
background: linear-gradient(
135deg in oklch,
oklch(0.65 0.20 280),
oklch(0.60 0.22 330),
oklch(0.65 0.20 20)
);
}
/* Mesh gradient (modern trend) */
.gradient-mesh {
background:
radial-gradient(at 0% 0%, oklch(0.70 0.15 280) 0%, transparent 50%),
radial-gradient(at 100% 0%, oklch(0.70 0.15 200) 0%, transparent 50%),
radial-gradient(at 100% 100%, oklch(0.70 0.15 330) 0%, transparent 50%),
oklch(0.97 0.01 250);
}
步骤 9:生成 Tailwind 配置
Tailwind v4(以 CSS 为先)——将色阶放入 @theme,即可生成 bg-brand-500、text-brand-900 等类名:
@import "tailwindcss";
@theme {
--color-brand-50: oklch(0.97 0.045 250);
--color-brand-500: oklch(0.55 0.15 250);
--color-brand-900: oklch(0.24 0.12 250);
/* ...every step 50-950, plus success/warning/error/info */
}
Tailwind v3(tailwind.config.js):
// tailwind.config.js
const brand = {
50: 'oklch(0.97 0.045 250)',
100: 'oklch(0.93 0.06 250)',
200: 'oklch(0.87 0.075 250)',
300: 'oklch(0.78 0.105 250)',
400: 'oklch(0.68 0.1275 250)',
500: 'oklch(0.55 0.15 250)',
600: 'oklch(0.48 0.15 250)',
700: 'oklch(0.40 0.15 250)',
800: 'oklch(0.32 0.135 250)',
900: 'oklch(0.24 0.12 250)',
950: 'oklch(0.16 0.105 250)',
};
module.exports = {
theme: {
extend: {
colors: {
brand,
success: { /* ... same pattern, hue 145 */ },
warning: { /* ... same pattern, hue 85 */ },
error: { /* ... same pattern, hue 25 */ },
info: { /* ... same pattern, hue 230 */ },
},
},
},
};
步骤 10:CSS 自定义属性架构
将令牌分为三层:
/* Layer 1: Primitive (raw values) */
:root {
--brand-500: oklch(0.55 0.15 250);
--error-500: oklch(0.55 0.18 25);
/* ...full --brand-*, --gray-*, semantic scales from Steps 2-4 */
}
/* Layer 2: Semantic (purpose) -- same names design-system uses */
:root {
--color-primary: var(--brand-500);
--interactive-primary: var(--brand-500);
--color-error: var(--error-500);
--bg-primary: var(--gray-50);
--text-primary: var(--gray-900);
--border-default: var(--gray-200);
}
/* Layer 3: Component (scoped) */
.btn-primary {
background: var(--interactive-primary);
color: white; /* verified >= 4.5:1 against brand-500 */
}
这种三层方法让主题化和深色模式变得轻而易举——只需重新映射第 2 层。
步骤 11:进行视觉验证并保存到设计上下文
- 渲染色板页面:编写一个小型 HTML 文件(临时文件),将每个色阶显示为一行色块,并标注令牌、hex 值以及相对于白色/黑色的对比度;另外添加浅色和深色模式下的示例文本/按钮组合。对其截图(例如
npx playwright screenshot --full-page "file://$PWD/swatches.html" swatches.png;深色模式截图时添加--color-scheme=dark),并使用 Read 查看图像。检查各步骤的间隔是否均匀,有无浑浊或荧光色,以及中性色是否呈现为中性。 - 询问是否要写回
.agents/design-context.md(design-context技能的文件)。只更新有变化的 Color System 行;切勿重写整个文件。将色阶映射到 design-context 角色,并使用 hex 值:
| design-context 角色 | 取值来源 |
|---|---|
| Primary | --brand-500(如果 500 与白色文本的对比度未达到 4.5:1,则使用 600) |
| Primary Light / Primary Dark | --brand-400 / --brand-700 |
| Secondary / Accent | 次要色 / 强调色相的 500 色阶 |
| Neutral 50 / 100 / 200 / 500 / 800 / 900 | 对应的 --gray-* 色阶 |
| Success / Warning / Error | --success-500 / --warning-500 / --error-500 |
向用户展示变更行的前后差异,并在保存前征求确认。
快速参考:从单个 Hex 生成调色板
- 读取
.agents/design-context.md;将品牌 hex 转换为 OKLCH。 - 提取色相,即为品牌色相。
- 使用步骤 2 中的明度映射生成 50–950 色阶。
- 使用品牌色相生成带色调的中性色(步骤 4)。
- 选择语义色相(步骤 3)。
- 使用
palette.py/contrast.py测量对比度(步骤 5)——继续之前先修复未通过的项。 - 构建深色模式映射(步骤 6)。
- 导出为 CSS 自定义属性或 Tailwind 配置。
- 渲染并检查色板页面;询问是否要将 hex 值保存到 design-context(步骤 11)。