AI 产品配色系统设计
AI 导读
AI 产品配色系统设计 色彩理论、语义化配色、暗黑模式、WCAG 无障碍与 Design Token 架构 一、为什么配色系统是 AI 产品的基础设施 颜色不是装饰,是信息。在 AI 产品中,颜色承载着状态、层级、操作和情感: 状态表达:成功(绿)、错误(红)、警告(黄)、加载中(蓝) 层级区分:主操作 vs 次要操作、已读 vs 未读、激活 vs 禁用 品牌识别:用户在 0.1...
AI 产品配色系统设计
色彩理论、语义化配色、暗黑模式、WCAG 无障碍与 Design Token 架构
一、为什么配色系统是 AI 产品的基础设施
颜色不是装饰,是信息。在 AI 产品中,颜色承载着状态、层级、操作和情感:
- 状态表达:成功(绿)、错误(红)、警告(黄)、加载中(蓝)
- 层级区分:主操作 vs 次要操作、已读 vs 未读、激活 vs 禁用
- 品牌识别:用户在 0.1 秒内通过颜色辨认产品
- 情感基调:冷静专业(蓝灰)、温暖友好(橙黄)、科技感(深紫+青)
配色系统不是"选几个好看的颜色",而是建立一套从语义到像素的映射规则,确保产品在任何场景下都能一致、可用、可访问。
配色系统的层级模型
┌───────────────────────────────────────────────┐
│ L1: Semantic Layer │
│ (success, error, warning, info, ...) │
├───────────────────────────────────────────────┤
│ L2: Palette Layer │
│ (blue-50 ~ blue-900, gray-50 ~ gray-900) │
├───────────────────────────────────────────────┤
│ L3: Primitive Layer │
│ (#1a365d, #2b6cb0, #3182ce, ...) │
└───────────────────────────────────────────────┘
使用方向:代码引用 L1 -> L1 映射到 L2 -> L2 定义为 L3
button.primary -> palette.blue.600 -> #2563eb
二、色彩理论基础
2.1 色彩模型选择
| 模型 | 用途 | 优势 |
|---|---|---|
| HSL | CSS 开发 | 直觉化(色相/饱和度/亮度) |
| OKLCH | 现代 CSS | 感知均匀,适合色板生成 |
| HEX/RGB | 存储和传输 | 通用、兼容性最好 |
| HSB | 设计工具 | Figma/Sketch 原生支持 |
2.2 科学化色板生成
传统的色板生成依赖设计师直觉。现代方法使用感知均匀色彩空间(OKLCH),确保同一色相的不同明度变体看起来"等距"。
// color-palette-generator.ts
interface PaletteStep {
name: string; // e.g., '50', '100', ..., '900'
hex: string;
hsl: { h: number; s: number; l: number };
contrastOnWhite: number;
contrastOnBlack: number;
}
function generatePalette(
baseColor: string,
steps: number = 10,
): PaletteStep[] {
/**
* Generate a 10-step palette from a base color using
* perceptually uniform lightness interpolation.
*
* Steps: 50 (lightest) -> 950 (darkest)
* Base color typically maps to step 500-600.
*/
const base = parseHSL(baseColor);
// Define lightness curve (perceptually uniform)
const lightnessSteps = [
{ name: '50', l: 97 },
{ name: '100', l: 93 },
{ name: '200', l: 85 },
{ name: '300', l: 75 },
{ name: '400', l: 63 },
{ name: '500', l: 50 },
{ name: '600', l: 40 },
{ name: '700', l: 32 },
{ name: '800', l: 24 },
{ name: '900', l: 15 },
];
return lightnessSteps.map(step => {
// Adjust saturation: slightly reduce at extremes
const satAdjust = step.l > 90 || step.l < 20
? 0.7
: step.l > 80 || step.l < 30
? 0.85
: 1.0;
const hsl = {
h: base.h,
s: Math.round(base.s * satAdjust),
l: step.l,
};
const hex = hslToHex(hsl);
return {
name: step.name,
hex,
hsl,
contrastOnWhite: calculateContrastRatio(hex, '#ffffff'),
contrastOnBlack: calculateContrastRatio(hex, '#000000'),
};
});
}
function hslToHex(hsl: { h: number; s: number; l: number }): string {
const { h, s, l } = hsl;
const a = s * Math.min(l, 100 - l) / 100;
const f = (n: number) => {
const k = (n + h / 30) % 12;
const color = l / 100 - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color)
.toString(16)
.padStart(2, '0');
};
return `#${f(0)}${f(8)}${f(4)}`;
}
三、语义化配色系统
3.1 从色板到语义
代码中不应该直接使用色板值(如 blue-600),而应该使用语义化名称(如 primary)。这使得主题切换成为一行配置修改。
// semantic-colors.ts
interface SemanticColors {
// Brand
primary: string; // Main brand action
primaryHover: string; // Hover state
primaryActive: string; // Active/pressed state
primarySubtle: string; // Soft background
// Feedback
success: string;
successSubtle: string;
warning: string;
warningSubtle: string;
error: string;
errorSubtle: string;
info: string;
infoSubtle: string;
// Surface
background: string; // Page background
surface: string; // Card/modal background
surfaceHover: string; // Hover state for interactive surfaces
overlay: string; // Modal backdrop
// Text
textPrimary: string; // Main body text
textSecondary: string; // Supporting text
textDisabled: string; // Disabled state
textInverse: string; // Text on dark backgrounds
textLink: string; // Links
textLinkHover: string;
// Border
border: string; // Default borders
borderFocus: string; // Focus ring
borderError: string; // Error state borders
// AI-specific
aiThinking: string; // Loading/processing indicator
aiResponse: string; // AI-generated content background
aiHighlight: string; // AI suggestions/highlights
streaming: string; // Streaming text cursor color
}
function createLightTheme(palette: {
blue: PaletteStep[];
gray: PaletteStep[];
green: PaletteStep[];
red: PaletteStep[];
yellow: PaletteStep[];
}): SemanticColors {
return {
primary: palette.blue[5].hex, // blue-500
primaryHover: palette.blue[6].hex, // blue-600
primaryActive: palette.blue[7].hex, // blue-700
primarySubtle: palette.blue[0].hex, // blue-50
success: palette.green[5].hex,
successSubtle: palette.green[0].hex,
warning: palette.yellow[4].hex,
warningSubtle: palette.yellow[0].hex,
error: palette.red[5].hex,
errorSubtle: palette.red[0].hex,
info: palette.blue[4].hex,
infoSubtle: palette.blue[0].hex,
background: '#ffffff',
surface: palette.gray[0].hex,
surfaceHover: palette.gray[1].hex,
overlay: 'rgba(0, 0, 0, 0.5)',
textPrimary: palette.gray[8].hex, // gray-800
textSecondary: palette.gray[5].hex, // gray-500
textDisabled: palette.gray[3].hex, // gray-300
textInverse: '#ffffff',
textLink: palette.blue[5].hex,
textLinkHover: palette.blue[6].hex,
border: palette.gray[2].hex,
borderFocus: palette.blue[4].hex,
borderError: palette.red[4].hex,
aiThinking: palette.blue[4].hex,
aiResponse: palette.blue[0].hex,
aiHighlight: palette.yellow[1].hex,
streaming: palette.blue[5].hex,
};
}
四、暗黑模式设计
4.1 暗黑模式不是"反转颜色"
暗黑模式的常见错误是简单地将黑白互换。正确的做法是重新映射整个语义层。
| 属性 | 浅色模式 | 暗黑模式 | 说明 |
|---|---|---|---|
| 背景 | #ffffff | #0f172a | 不用纯黑,用深蓝灰 |
| 表面 | #f8fafc | #1e293b | 卡片/弹窗略浅于背景 |
| 主文本 | #1e293b | #e2e8f0 | 不用纯白,减少眩光 |
| 次文本 | #64748b | #94a3b8 | 保持对比度比例 |
| 主色调 | #2563eb | #60a5fa | 暗黑下用更亮的主色 |
| 错误 | #dc2626 | #f87171 | 暗背景上用更亮的红 |
4.2 双主题 Token 系统
// theme-tokens.ts
type ThemeMode = 'light' | 'dark';
interface ThemeTokens {
mode: ThemeMode;
colors: SemanticColors;
}
function createDarkTheme(palette: typeof lightPalette): SemanticColors {
return {
primary: palette.blue[3].hex, // blue-300 (brighter)
primaryHover: palette.blue[2].hex, // blue-200
primaryActive: palette.blue[4].hex, // blue-400
primarySubtle: '#1e3a5f', // Custom dark blue
success: palette.green[3].hex,
successSubtle: '#1a3a2a',
warning: palette.yellow[3].hex,
warningSubtle: '#3a3520',
error: palette.red[3].hex,
errorSubtle: '#3a1a1a',
info: palette.blue[3].hex,
infoSubtle: '#1a2a3a',
background: '#0f172a', // slate-900
surface: '#1e293b', // slate-800
surfaceHover: '#334155', // slate-700
overlay: 'rgba(0, 0, 0, 0.7)',
textPrimary: '#e2e8f0', // slate-200
textSecondary: '#94a3b8', // slate-400
textDisabled: '#475569', // slate-600
textInverse: '#0f172a',
textLink: palette.blue[3].hex,
textLinkHover: palette.blue[2].hex,
border: '#334155', // slate-700
borderFocus: palette.blue[3].hex,
borderError: palette.red[3].hex,
aiThinking: palette.blue[3].hex,
aiResponse: '#1e3a5f',
aiHighlight: '#3a3520',
streaming: palette.blue[3].hex,
};
}
// CSS Custom Properties export
function exportAsCSSVariables(tokens: SemanticColors): string {
const entries = Object.entries(tokens).map(([key, value]) => {
const cssName = key.replace(/([A-Z])/g, '-$1').toLowerCase();
return ` --color-${cssName}: ${value};`;
});
return `:root {\n${entries.join('\n')}\n}`;
}
function exportAsDarkCSS(tokens: SemanticColors): string {
const entries = Object.entries(tokens).map(([key, value]) => {
const cssName = key.replace(/([A-Z])/g, '-$1').toLowerCase();
return ` --color-${cssName}: ${value};`;
});
return `@media (prefers-color-scheme: dark) {\n :root {\n${entries.join('\n')}\n }\n}`;
}
五、WCAG 无障碍设计
5.1 对比度要求
| 级别 | 正文 (>= 18px bold / 24px) | 小文本 (< 18px bold) | 非文本元素 |
|---|---|---|---|
| AA | 3:1 | 4.5:1 | 3:1 |
| AAA | 4.5:1 | 7:1 | - |
5.2 自动对比度校验
// wcag-validator.ts
function relativeLuminance(hex: string): number {
const rgb = hexToRGB(hex);
const [r, g, b] = [rgb.r, rgb.g, rgb.b].map(c => {
const sRGB = c / 255;
return sRGB <= 0.03928
? sRGB / 12.92
: Math.pow((sRGB + 0.055) / 1.055, 2.4);
});
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
function contrastRatio(color1: string, color2: string): number {
const l1 = relativeLuminance(color1);
const l2 = relativeLuminance(color2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
interface ContrastReport {
pair: [string, string];
ratio: number;
aa: { normalText: boolean; largeText: boolean };
aaa: { normalText: boolean; largeText: boolean };
}
function auditThemeContrast(
theme: SemanticColors,
): ContrastReport[] {
/**
* Audit all critical color pairings in the theme.
* Returns list of pairings that fail WCAG criteria.
*/
const criticalPairs: [string, string, string, string][] = [
// [name1, color1, name2, color2]
['textPrimary', theme.textPrimary, 'background', theme.background],
['textSecondary', theme.textSecondary, 'background', theme.background],
['textPrimary', theme.textPrimary, 'surface', theme.surface],
['primary', theme.primary, 'background', theme.background],
['error', theme.error, 'background', theme.background],
['success', theme.success, 'background', theme.background],
['textInverse', theme.textInverse, 'primary', theme.primary],
];
return criticalPairs.map(([name1, color1, name2, color2]) => {
const ratio = contrastRatio(color1, color2);
return {
pair: [color1, color2],
ratio,
aa: {
normalText: ratio >= 4.5,
largeText: ratio >= 3,
},
aaa: {
normalText: ratio >= 7,
largeText: ratio >= 4.5,
},
};
});
}
function fixContrast(
foreground: string,
background: string,
targetRatio: number = 4.5,
): string {
/**
* Adjust foreground color to meet target contrast ratio.
* Preserves hue, adjusts lightness.
*/
let hsl = parseHSL(foreground);
const bgLuminance = relativeLuminance(background);
// Direction: darken on light background, lighten on dark
const step = bgLuminance > 0.5 ? -2 : 2;
for (let i = 0; i < 50; i++) {
const hex = hslToHex(hsl);
const ratio = contrastRatio(hex, background);
if (ratio >= targetRatio) return hex;
hsl = { ...hsl, l: Math.max(0, Math.min(100, hsl.l + step)) };
}
// Fallback: return black or white
return bgLuminance > 0.5 ? '#000000' : '#ffffff';
}
六、Design Token 架构
6.1 Token 分发流程
Figma (设计师)
|
v
[Figma Tokens Plugin] -> tokens.json (原始 Token)
|
v
[Style Dictionary] -> 转换为多平台格式
|
├── CSS Variables (Web)
├── Tailwind Config (Tailwind CSS)
├── Swift UIColor (iOS)
├── Android XML (Android)
└── JSON (服务端/PPT 生成)
6.2 Token 结构示例
{
"color": {
"primitive": {
"blue": {
"50": { "value": "#eff6ff" },
"100": { "value": "#dbeafe" },
"200": { "value": "#bfdbfe" },
"500": { "value": "#3b82f6" },
"700": { "value": "#1d4ed8" },
"900": { "value": "#1e3a8a" }
}
},
"semantic": {
"primary": {
"value": "{color.primitive.blue.500}",
"description": "Primary brand color, used for main CTAs"
},
"primary-hover": {
"value": "{color.primitive.blue.600}"
},
"text-primary": {
"value": "{color.primitive.gray.800}",
"darkValue": "{color.primitive.gray.200}"
}
}
},
"spacing": {
"xs": { "value": "4px" },
"sm": { "value": "8px" },
"md": { "value": "16px" },
"lg": { "value": "24px" },
"xl": { "value": "32px" }
},
"typography": {
"heading": {
"fontFamily": { "value": "Inter, system-ui, sans-serif" },
"fontWeight": { "value": "700" },
"lineHeight": { "value": "1.2" }
}
}
}
6.3 Tailwind 集成
// tailwind.config.ts
import type { Config } from 'tailwindcss';
import tokens from './tokens.json';
const config: Config = {
theme: {
extend: {
colors: {
primary: {
DEFAULT: tokens.color.semantic.primary.value,
hover: tokens.color.semantic['primary-hover'].value,
subtle: tokens.color.primitive.blue['50'].value,
},
surface: tokens.color.semantic.surface.value,
// ... map all semantic tokens
},
spacing: {
xs: tokens.spacing.xs.value,
sm: tokens.spacing.sm.value,
md: tokens.spacing.md.value,
lg: tokens.spacing.lg.value,
xl: tokens.spacing.xl.value,
},
},
},
};
export default config;
七、AI 产品的特殊配色需求
AI 交互状态的颜色语义
| 状态 | 推荐颜色 | 动态效果 | 说明 |
|---|---|---|---|
| 等待输入 | 中性灰 | 无 | 不引导、不催促 |
| AI 思考中 | 品牌蓝 | 脉冲/渐变动画 | 表示"正在工作" |
| AI 回复中 | 浅蓝背景 | 流式文字光标 | 区分于用户消息 |
| AI 完成 | 标准背景 | 无 | 过渡回正常状态 |
| AI 错误 | 红色提示 | 无 | 但要提供重试按钮 |
| AI 建议 | 黄色高亮 | 淡入 | 引起注意但不打扰 |
代码示例:AI 消息气泡
/* AI message bubble - distinct from user messages */
.message-ai {
background: var(--color-ai-response); /* 浅蓝 */
border-left: 3px solid var(--color-primary); /* 品牌色标识 */
color: var(--color-text-primary);
}
.message-user {
background: var(--color-surface); /* 中性灰 */
color: var(--color-text-primary);
}
/* AI thinking indicator */
.ai-thinking {
display: inline-flex;
gap: 4px;
}
.ai-thinking .dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--color-ai-thinking);
animation: pulse 1.4s ease-in-out infinite;
}
八、总结
配色系统的最终目标是让颜色"隐形"——用户不会注意到颜色,但会因为颜色而更高效地使用产品。好的配色系统应该:
- 语义化:开发者用
primary而非#2563eb - 可主题化:一行配置切换明暗主题
- 可验证:自动化工具检测对比度和一致性
- 可扩展:新增颜色不破坏现有体系
- 可感知:色板在感知上均匀分布
Maurice | [email protected]
深度加工(NotebookLM 生成)
基于本文内容生成的 PPT 大纲、博客摘要、短视频脚本与 Deep Dive 播客,用于多场景复用
PPT 大纲(5-8 张幻灯片) 点击展开
AI 产品配色系统设计 — ppt
这是一份基于您提供的文章内容提取的 PPT 大纲,共包含 7 张幻灯片。
幻灯片 1:AI 产品配色系统的核心价值
- 颜色即信息:在 AI 产品中,颜色不仅仅是装饰,它更是状态表达、层级区分、品牌识别与情感基调的核心载体 [1]。
- 建立系统化映射规则:优秀的配色系统绝非“选几个好看的颜色”,而是建立一套从语义到像素的严谨映射规则,以确保跨场景的可用性 [1]。
- 三层架构模型:科学的系统包含顶层语义层(Semantic,如
success)、中层色板层(Palette,如blue-600)和底层原始色层(Primitive,如#2563eb) [1]。
幻灯片 2:色彩理论与科学化色板生成
- 多维度色彩模型应用:根据使用场景选择最优模型,例如 CSS 开发使用直觉化的 HSL,而现代色板生成应采用 OKLCH 模型 [1]。
- 感知均匀色彩空间:摒弃传统单纯依赖设计师直觉的做法,使用 OKLCH 确保同一色相不同明度(Lightness)的变体在人眼视觉上保持“等距” [1]。
- 算法驱动生成:通过定义科学的亮度曲线进行插值,并对极端亮度下的饱和度进行微调,算法化生成从 50 到 950 的完整色板梯度 [1]。
幻灯片 3:语义化配色系统的构建
- 面向意图命名代码变量:在代码中彻底抛弃直接引用色板值,改用
primary等语义化名称,实现只需一行配置即可完成的主题切换 [2]。 - 精细化状态与反馈色彩:系统地定义成功(绿)、警告(黄)、错误(红)等交互反馈色,并为其配备对应的弱化背景色(Subtle) [2, 3]。
- 界面元素解耦分类:将色彩应用场景精细划分为品牌(Brand)、反馈(Feedback)、表面背景(Surface)、文本(Text)与边框(Border)等核心维度 [2, 3]。
幻灯片 4:暗黑模式与 WCAG 无障碍设计
- 暗黑模式重写而非反转:暗黑模式绝非简单的黑白互换,正确的做法是重新映射语义层,例如使用深蓝灰替代纯黑背景 [4]。
- 针对暗环境的对比度优化:在暗色背景下,应使用亮度更高、更鲜明的主色和反馈色,以保证信息清晰度并有效减少视觉眩光 [4]。
- 严格遵守 WCAG 对比度标准:确保设计符合无障碍规范,普通正文文本需满足 AA 级(4.5:1)或更高的 AAA 级(7:1)对比度要求 [5]。
- 自动化对比度校验机制:引入代码层面的审查工具(WCAG Validator),自动检测关键颜色配对的对比度,并在不达标时自动微调亮度进行修复 [5, 6]。
幻灯片 5:Design Token 架构与多端分发
- 构建唯一的真实数据源:以设计师的 Figma 插件导出的
tokens.json作为整个产品线的原始 Token 核心数据源 [6]。 - 自动化多平台格式转换:通过引入 Style Dictionary 工具,将基础 Token 一键转换为前端 CSS 变量、iOS 以及 Android 等多平台所需的专属格式 [6]。
- 前端框架深度集成:实现与 Tailwind CSS 等现代前端开发框架的无缝集成,将语义化 Token 映射为开发者可直接调用的样式工具类 [6]。
幻灯片 6:AI 产品的特殊配色需求
- 定义 AI 专属状态语义:为 AI 交互生命周期提供专属色彩定义,如“等待输入”、“AI 思考中”、“回复中”、“完成”及“建议高亮” [3, 6]。
- 视觉隔离的对话气泡设计:通过品牌色边框和特定的浅蓝背景色(
aiResponse),在视觉上将 AI 生成的内容与用户的消息清晰区分 [3, 6]。 - 融合动态交互色彩效果:将颜色与动态效果结合,例如在 AI“思考中”状态使用品牌蓝搭配平缓的脉冲动画,表示正在工作且不引起用户的催促焦虑 [6]。
幻灯片 7:总结:优秀配色系统的核心目标
- 让颜色“隐形”:极致的设计让用户察觉不到颜色的刻意存在,却能因为合理的色彩引导而更高效地使用 AI 产品 [6]。
- 全面语义化:使开发者脱离十六进制代码的束缚,通过语义化命名实现高效开发与全站一致性 [6]。
- 高度可扩展与可主题化:确保系统在新增色彩或切换明暗主题时,不会破坏现有的整体视觉架构 [6]。
- 自动化与可验证:通过工程化工具保障色彩体系在人眼感知上的均匀分布,并自动拦截不符合无障碍标准的配色方案 [6]。
博客摘要 + 核心看点 点击展开
AI 产品配色系统设计 — summary
SEO 友好博客摘要
这篇深度指南详细解析了如何构建专业且可扩展的 AI 产品配色系统。在 AI 产品中,颜色不仅是装饰,更是传递状态、层级与情感的核心信息 [1]。文章系统拆解了底层的 OKLCH 色彩理论、L1至L3的语义化配色分层模型,以及暗黑模式科学重映射的设计方法 [1, 2]。此外,还探讨了如何基于 WCAG 规范集成自动化对比度校验机制,并利用 Design Token 架构打通多端工作流 [3, 4]。针对 AI 特殊交互场景(如“思考中”、“流式回复”),指南给出了专属色彩语义参考,助您打造一致、无障碍的现代 AI 产品基础设施 [4]。
3 条核心看点
- 科学的语义化分层:基于 OKLCH 空间生成等距色板,构建 L1至L3 的语义化色彩映射架构 [1]。
- 暗黑模式与无障碍:暗黑模式需重映射语义层而非简单反色,并结合 WCAG 自动化校验对比度 [2, 3]。
- AI 专属交互与基建:定制 AI 思考等特殊状态色彩,并借助 Design Token 架构实现多端同步 [4]。
60 秒短视频脚本 点击展开
AI 产品配色系统设计 — video
这是一段为您定制的 60 秒短视频脚本,严格遵循了您的字数和结构要求:
【钩子开场】(12字)
AI颜色不是装饰,而是信息![1]
【核心解说 1:语义映射】(28字)
建立语义到像素映射,代码用语义代替具体色值,一键切换主题。[1][2]
【核心解说 2:暗黑与无障碍】(30字)
暗黑模式绝非简单的黑白反转,需重新映射语义层并自动校验对比度。[3][4]
【核心解说 3:AI 专属状态】(29字)
AI交互有专属色彩:品牌蓝代表思考,黄色高亮建议,引导操作。[5]
【一句收束】
好的配色系统是“隐形”的,它能让用户在不知不觉中更高效地使用产品。[5]
课后巩固
与本文内容匹配的闪卡与测验,帮助巩固所学知识
延伸阅读
根据本文主题,为你推荐相关的学习资料