关于 color-palette
此技能将单个品牌十六进制颜色转换为 Tailwind v4 色彩系统,并映射语义令牌及明暗模式。还涵盖对比度检查和无障碍调整。
主要功能
- 01生成 11 阶色阶
- 02将色阶映射到语义令牌
- 03定义明暗模式的配色对
- 04检查 WCAG 对比度
- 05输出 Tailwind v4 CSS
适用场景
- 01根据品牌色创建主题
- 02设置成对的背景与前景令牌
- 03检查两种模式下的文本和界面对比度
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill color-palette安装后在 Claude Code 中输入/color-palette确认已加载。
附带文件assets/tailwind-colors.css
详细介绍
色板生成器
从单个品牌色十六进制值生成完整、无障碍的色彩系统。生成可直接粘贴到项目中的 Tailwind v4 CSS。
工作流
步骤 1:获取品牌色十六进制值
询问主品牌色。只需提供一个十六进制值,例如 #0D9488。
步骤 2:生成 11 阶色阶
将十六进制值转换为 HSL,然后在保持色相不变的同时调整亮度,生成不同深浅的颜色。
十六进制转 HSL
function hexToHSL(hex) {
hex = hex.replace(/^#/, '');
const r = parseInt(hex.substring(0, 2), 16) / 255;
const g = parseInt(hex.substring(2, 4), 16) / 255;
const b = parseInt(hex.substring(4, 6), 16) / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const diff = max - min;
let l = (max + min) / 2;
let s = 0;
if (diff !== 0) {
s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min);
}
let h = 0;
if (diff !== 0) {
if (max === r) h = ((g - b) / diff + (g < b ? 6 : 0)) / 6;
else if (max === g) h = ((b - r) / diff + 2) / 6;
else h = ((r - g) / diff + 4) / 6;
}
return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
}
亮度和饱和度值
| 色阶 | 亮度 | 饱和度乘数 | 使用场景 |
|---|---|---|---|
| 50 | 97% | 0.80 | 低调的背景 |
| 100 | 94% | 0.80 | 悬停状态 |
| 200 | 87% | 0.85 | 边框、分隔线 |
| 300 | 75% | 0.90 | 禁用状态 |
| 400 | 62% | 0.95 | 占位文本 |
| 500 | 48% | 1.00 | 品牌色基准 |
| 600 | 40% | 1.00 | 主要操作(通常为品牌色) |
| 700 | 33% | 1.00 | 主按钮悬停状态 |
| 800 | 27% | 1.00 | 激活状态 |
| 900 | 20% | 1.00 | 浅色背景上的文本 |
| 950 | 10% | 1.00 | 最深的强调色 |
降低浅色阶的饱和度(50-200 降低 15-20%,300-400 降低 5-10%),避免生成过于鲜艳的粉彩色。500-950 保持完整饱和度。
完整色阶生成器
function generateShadeScale(brandHex) {
const { h, s } = hexToHSL(brandHex);
const shades = {
50: { l: 97, sMul: 0.8 }, 100: { l: 94, sMul: 0.8 },
200: { l: 87, sMul: 0.85 }, 300: { l: 75, sMul: 0.9 },
400: { l: 62, sMul: 0.95 }, 500: { l: 48, sMul: 1.0 },
600: { l: 40, sMul: 1.0 }, 700: { l: 33, sMul: 1.0 },
800: { l: 27, sMul: 1.0 }, 900: { l: 20, sMul: 1.0 },
950: { l: 10, sMul: 1.0 }
};
const result = {};
for (const [shade, { l, sMul }] of Object.entries(shades)) {
result[shade] = `hsl(${h}, ${Math.round(s * sMul)}%, ${l}%)`;
}
return result;
}
HSL 转十六进制
function hslToHex(h, s, l) {
s = s / 100; l = l / 100;
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs((h / 60) % 2 - 1));
const m = l - c / 2;
let r = 0, g = 0, b = 0;
if (h < 60) { r = c; g = x; }
else if (h < 120) { r = x; g = c; }
else if (h < 180) { g = c; b = x; }
else if (h < 240) { g = x; b = c; }
else if (h < 300) { r = x; b = c; }
else { r = c; b = x; }
r = Math.round((r + m) * 255);
g = Math.round((g + m) * 255);
b = Math.round((b + m) * 255);
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`.toUpperCase();
}
验证
生成的色阶应属于同一色系,并呈现平滑的渐变。浅色阶(50-300)可用于背景,深色阶(700-950)可用于文本。500-700 应能辨认出品牌色。
步骤 3:映射语义令牌
每个背景令牌都 MUST 配有对应的前景令牌。切勿单独使用背景,否则深色模式会出问题。
浅色模式令牌
| 令牌 | 色阶 | 使用场景 |
|---|---|---|
background | 白色 | 页面背景 |
foreground | 950 | 正文 |
card | 白色 | 卡片背景 |
card-foreground | 900 | 卡片文本 |
popover | 白色 | 下拉菜单/工具提示背景 |
popover-foreground | 950 | 下拉菜单文本 |
primary | 600 | 主按钮、链接 |
primary-foreground | 白色 | 主按钮上的文本 |
secondary | 100 | 次要按钮 |
secondary-foreground | 900 | 次要按钮上的文本 |
muted | 50 | 禁用状态背景、低调的区块 |
muted-foreground | 600 | 弱化文本、说明文字 |
accent | 100 | 悬停状态、低调的高亮 |
accent-foreground | 900 | 强调色背景上的文本 |
destructive | 红色-600 | 删除按钮、错误 |
destructive-foreground | 白色 | 危险操作按钮上的文本 |
border | 200 | 输入框边框、分隔线 |
input | 200 | 输入字段边框 |
ring | 600 | 焦点环 |
深色模式令牌
| 令牌 | 色阶 | 使用场景 |
|---|---|---|
background | 950 | 页面背景 |
foreground | 50 | 正文 |
card | 900 | 卡片背景 |
card-foreground | 50 | 卡片文本 |
popover | 900 | 下拉菜单背景 |
popover-foreground | 50 | 下拉菜单文本 |
primary | 500 | 主按钮(深色模式下更明亮) |
primary-foreground | 白色 | 主按钮上的文本 |
secondary | 800 | 次要按钮 |
secondary-foreground | 50 | 次要按钮上的文本 |
muted | 800 | 禁用状态背景 |
muted-foreground | 400 | 弱化文本 |
accent | 800 | 悬停状态 |
accent-foreground | 50 | 强调色背景上的文本 |
destructive | 红色-500 | 删除按钮(更明亮) |
destructive-foreground | 白色 | 危险操作上的文本 |
border | 800 | 边框 |
input | 800 | 输入框边框 |
ring | 500 | 焦点环 |
深色模式反转模式
深色模式会反转明度,同时保留色相和饱和度。交换两端的色阶(50 变为 950,950 变为 50),中间色阶保持不变(500 仍接近 500)。
| 浅色模式色阶 | 深色模式对应色阶 | 用途 |
|---|---|---|
| 50 | 950 | 背景 |
| 100 | 900 | 微妙背景 |
| 200 | 800 | 边框 |
| 500 | 500(稍亮) | 品牌基准色 |
| 600 | 400 | 主要操作 |
| 950 | 50 | 文本颜色 |
深色模式的关键原则:
- 主要颜色使用色阶 500(而不是 600)——在深色背景上更亮、更醒目
- 文本使用色阶 50(米白色),而不是纯
#FFFFFF——更护眼 - 边框需要比背景亮约 10-15%(例如,在 950 背景上使用 800 边框)
- 海拔越高,颜色越亮(与浅色模式中的阴影相反)
- 更改背景时,始终同时更新前景
第 4 步:检查对比度
WCAG 最低对比度
| 内容类型 | AA | AAA |
|---|---|---|
| 正常文本(<18px 或 <14px 粗体) | 4.5:1 | 7:1 |
| 大号文本(>=18px 或 >=14px 粗体) | 3:1 | 4.5:1 |
| UI 组件(按钮、边框) | 3:1 | 未定义 |
| 图形对象(图标、图表) | 3:1 | 未定义 |
大多数项目以 AA 为目标;对无障碍要求较高的场景(政府、医疗)以 AAA 为目标。
亮度与对比度公式
function getLuminance(hex) {
hex = hex.replace(/^#/, '');
const r = parseInt(hex.substring(0, 2), 16) / 255;
const g = parseInt(hex.substring(2, 4), 16) / 255;
const b = parseInt(hex.substring(4, 6), 16) / 255;
const rsRGB = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
const gsRGB = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4);
const bsRGB = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4);
return 0.2126 * rsRGB + 0.7152 * gsRGB + 0.0722 * bsRGB;
}
function getContrastRatio(hex1, hex2) {
const lum1 = getLuminance(hex1);
const lum2 = getLuminance(hex2);
const lighter = Math.max(lum1, lum2);
const darker = Math.min(lum1, lum2);
return (lighter + 0.05) / (darker + 0.05);
}
快速检查表 —— 浅色模式
| 前景 | 背景 | 比率 | 是否通过? | 使用场景 |
|---|---|---|---|---|
| 950 | 白色 | 18.5:1 | AAA | 正文 |
| 900 | 白色 | 14.2:1 | AAA | 卡片文本 |
| 700 | 白色 | 8.1:1 | AAA | 文本 |
| 600 | 白色 | 5.7:1 | AA | 文本、按钮 |
| 500 | 白色 | 3.9:1 | 未通过 | 作为文本时太浅 |
| 白色 | 600 | 5.7:1 | AA | 按钮文本 |
| 白色 | 700 | 8.1:1 | AAA | 按钮文本 |
| 600 | 50 | 5.4:1 | AA | 弱化区块文本 |
快速检查表 —— 深色模式
| 前景 | 背景 | 比率 | 是否通过? | 使用场景 |
|---|---|---|---|---|
| 50 | 950 | 18.5:1 | AAA | 正文 |
| 50 | 900 | 14.2:1 | AAA | 卡片文本 |
| 400 | 950 | 8.2:1 | AAA | 弱化文本 |
| 400 | 900 | 6.3:1 | AA | 弱化文本 |
| 白色 | 600 | 5.7:1 | AA | 按钮文本 |
经验法则:对于文本,前景与背景的明度差应达到 50% 或以上。
需要验证的关键组合
- 正文:背景上的前景(浅色模式:白色上的 950 = 18.5:1,深色模式:950 上的 50 = 18.5:1)
- 主要按钮:主要前景色叠加在主要颜色上(浅色模式:600 上的白色 = 5.7:1,深色模式:500 上的白色 = 3.9:1——处于临界值)
- 弱化文本:弱化前景色叠加在弱化颜色上(浅色模式:50 上的 600 = 5.4:1,深色模式:800 上的 400 = 4.1:1——可能不达标)
- 卡片文本:卡片前景色叠加在卡片上(浅色模式:白色上的 900 = 14.2:1,深色模式:900 上的 50 = 14.2:1)
修复常见的对比度问题
白色文本叠加在 primary-500 上未达标(3.9:1):改用 primary-600(5.7:1),或在按钮上使用深色文本。
深色模式中的弱化文本未达标(800 上的 400 = 4.1:1):改用 900 上的 300 = 6.8:1。
链接不够醒目(白色上的 500 = 3.9:1):改用 primary-700(8.1:1),或添加下划线装饰。
第 5 步:输出 Tailwind v4 CSS
@import "tailwindcss";
@theme {
/* Shade scale */
--color-primary-50: #F0FDFA;
--color-primary-100: #CCFBF1;
--color-primary-200: #99F6E4;
--color-primary-300: #5EEAD4;
--color-primary-400: #2DD4BF;
--color-primary-500: #14B8A6;
--color-primary-600: #0D9488;
--color-primary-700: #0F766E;
--color-primary-800: #115E59;
--color-primary-900: #134E4A;
--color-primary-950: #042F2E;
/* Light mode semantic tokens */
--color-background: #FFFFFF;
--color-foreground: var(--color-primary-950);
--color-card: #FFFFFF;
--color-card-foreground: var(--color-primary-900);
--color-popover: #FFFFFF;
--color-popover-foreground: var(--color-primary-950);
--color-primary: var(--color-primary-600);
--color-primary-foreground: #FFFFFF;
--color-secondary: var(--color-primary-100);
--color-secondary-foreground: var(--color-primary-900);
--color-muted: var(--color-primary-50);
--color-muted-foreground: var(--color-primary-600);
--color-accent: var(--color-primary-100);
--color-accent-foreground: var(--color-primary-900);
--color-destructive: #DC2626;
--color-destructive-foreground: #FFFFFF;
--color-border: var(--color-primary-200);
--color-input: var(--color-primary-200);
--color-ring: var(--color-primary-600);
--radius: 0.5rem;
}
/* Dark mode overrides */
.dark {
--color-background: var(--color-primary-950);
--color-foreground: var(--color-primary-50);
--color-card: var(--color-primary-900);
--color-card-foreground: var(--color-primary-50);
--color-popover: var(--color-primary-900);
--color-popover-foreground: var(--color-primary-50);
--color-primary: var(--color-primary-500);
--color-primary-foreground: #FFFFFF;
--color-secondary: var(--color-primary-800);
--color-secondary-foreground: var(--color-primary-50);
--color-muted: var(--color-primary-800);
--color-muted-foreground: var(--color-primary-400);
--color-accent: var(--color-primary-800);
--color-accent-foreground: var(--color-primary-50);
--color-destructive: #EF4444;
--color-destructive-foreground: #FFFFFF;
--color-border: var(--color-primary-800);
--color-input: var(--color-primary-800);
--color-ring: var(--color-primary-500);
}
将 assets/tailwind-colors.css 复制为起始模板。
组件用法示例
// Primary button
<button className="bg-primary text-primary-foreground hover:bg-primary/90">Click me</button>
// Secondary button
<button className="bg-secondary text-secondary-foreground hover:bg-secondary/80">Cancel</button>
// Card
<div className="bg-card text-card-foreground border-border rounded-lg">
<h2>Title</h2>
<p className="text-muted-foreground">Description</p>
</div>
// Input
<input className="bg-background text-foreground border-input focus:ring-ring" />
常见调整
- 浅色阶过于鲜艳:将饱和度降低 10-20%
- 主要颜色的对比度不佳:文本使用色阶 700 或更高
- 深色模式太暗:背景使用色阶 900,而不是 950
- 品牌颜色太浅或太深:调整到色阶 500-600 范围
- 深色模式看起来发灰:主要颜色使用色阶 500(比浅色模式的 600 更亮)
- 深色模式中纯白文本过于刺眼:改用色阶 50(米白色)
- 深色模式中的弱化文本对比度未达标:使用差异更大的色阶(在 900 上使用 300,而不是在 800 上使用 400)
品牌形象调整
- 保守型品牌(金融、法律):按钮使用 primary-700,降低浅色的饱和度
- 活力型品牌(创意、科技):使用 primary-500-600,保持完整饱和度
- 极简型品牌(设计、建筑):谨慎使用 primary,突出柔和色调和细腻的边框(primary-100)
验证清单
- 正文:>=4.5:1(普通文本)或 >=3:1(大号文本)
- 主按钮文本:>=4.5:1
- 次按钮文本:>=4.5:1
- 柔和文本:>=4.5:1
- 链接:>=4.5:1(或添加下划线)
- UI 元素(边框):>=3:1
- 焦点指示器:>=3:1
- 错误文本:>=4.5:1
- 深色模式:通过上述所有检查
- 每种背景都有对应的前景色
- 品牌色在两种模式下都易于辨认
- 边框清晰可见但不过于刺眼
- 卡片/区块的边界清晰
发布前请测试两种模式。
可选参考资料
- 在线对比度检查工具:WebAIM (webaim.org/resources/contrastchecker)、Coolors (coolors.co/contrast-checker)、Accessible Colors (accessible-colors.com)
- CI/CD 对比度测试:在测试套件中使用
getContrastRatio(),断言所有令牌对都达到最低比率 - 透明度/渐变边缘情况:对于带透明度的颜色,根据最终渲染颜色计算。对于渐变,检查两个端点。
- OLED 深色模式:使用
@media (prefers-contrast: high)和#000000背景,以节省 AMOLED 屏幕的电量 - 多色调色板:为每种品牌色生成单独的色阶,并映射到不同的语义角色(主色、强调色)
- 调色板可视化工具:coolors.co、paletton.com、Figma 色板
assets/tailwind-colors.css— 完整的 CSS 输出模板