3.8.3 颜色对比度
一句话破题
足够的对比度让所有人都能看清你的内容,包括在阳光下看手机的普通用户。
核心价值
低对比度的文字让视力正常的人在光线不好时也难以阅读。WCAG 规定了最低对比度要求,满足它对所有用户都有益。
WCAG 对比度要求
| 级别 | 普通文字 | 大字/粗体 | 说明 |
|---|---|---|---|
| AA | 4.5:1 | 3:1 | 最低要求 |
| AAA | 7:1 | 4.5:1 | 更高标准 |
大字定义:18pt (24px) 以上,或 14pt (18.5px) 加粗
对比度计算工具
tsx
// 对比度计算函数
function getLuminance(r: number, g: number, b: number): number {
const [rs, gs, bs] = [r, g, b].map((c) => {
c = c / 255
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
})
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs
}
function getContrastRatio(color1: string, color2: string): number {
const rgb1 = hexToRgb(color1)
const rgb2 = hexToRgb(color2)
const l1 = getLuminance(rgb1.r, rgb1.g, rgb1.b)
const l2 = getLuminance(rgb2.r, rgb2.g, rgb2.b)
const lighter = Math.max(l1, l2)
const darker = Math.min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
function hexToRgb(hex: string) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
} : { r: 0, g: 0, b: 0 }
}
// 使用
const ratio = getContrastRatio('#3B82F6', '#FFFFFF')
console.log(`对比度: ${ratio.toFixed(2)}:1`) // 4.51:1常见问题配色
| 问题 | 差的配色 | 好的配色 |
|---|---|---|
| 灰色文字 | #999 on #fff (2.85:1) | #666 on #fff (5.74:1) |
| 蓝色链接 | #60A5FA on #fff (2.77:1) | #2563EB on #fff (4.88:1) |
| 成功色 | #22C55E on #fff (2.43:1) | #15803D on #fff (4.96:1) |
安全配色方案
css
:root {
/* 确保满足 AA 标准的配色 */
/* 文字颜色 */
--text-primary: #111827; /* on white: 16.02:1 */
--text-secondary: #4B5563; /* on white: 7.50:1 */
--text-muted: #6B7280; /* on white: 5.24:1 */
/* 按钮颜色 */
--btn-primary: #2563EB; /* white text: 4.88:1 */
--btn-success: #15803D; /* white text: 4.96:1 */
--btn-danger: #DC2626; /* white text: 4.51:1 */
/* 链接 */
--link-color: #1D4ED8; /* on white: 6.95:1 */
}不只依赖颜色
色盲用户可能无法区分红/绿,所以不要只用颜色传达信息:
tsx
// 差:只用颜色区分状态
<span className="text-green-500">成功</span>
<span className="text-red-500">失败</span>
// 好:颜色 + 图标
<span className="text-green-600 flex items-center">
<CheckIcon className="w-4 h-4 mr-1" />
成功
</span>
<span className="text-red-600 flex items-center">
<XIcon className="w-4 h-4 mr-1" />
失败
</span>
// 表单错误:颜色 + 图标 + 位置
<div>
<input
className={cn(
"border rounded",
error ? "border-red-500" : "border-gray-300"
)}
aria-invalid={!!error}
aria-describedby={error ? "email-error" : undefined}
/>
{error && (
<p id="email-error" className="text-red-600 text-sm mt-1 flex items-center">
<AlertCircle className="w-4 h-4 mr-1" />
{error}
</p>
)}
</div>图表可访问性
tsx
// 差:只用颜色区分数据系列
<LineChart data={data}>
<Line stroke="#3B82F6" />
<Line stroke="#10B981" />
<Line stroke="#F59E0B" />
</LineChart>
// 好:颜色 + 形状/线型
<LineChart data={data}>
<Line
stroke="#3B82F6"
strokeDasharray="none"
dot={{ shape: 'circle' }}
/>
<Line
stroke="#10B981"
strokeDasharray="5 5"
dot={{ shape: 'square' }}
/>
<Line
stroke="#F59E0B"
strokeDasharray="10 3"
dot={{ shape: 'triangle' }}
/>
<Legend />
</LineChart>焦点状态对比度
css
/* 焦点轮廓需要足够对比度 */
:focus-visible {
outline: 2px solid #2563EB;
outline-offset: 2px;
}
/* 在深色背景上 */
.dark :focus-visible {
outline: 2px solid #60A5FA;
}
/* 高对比度模式 */
@media (prefers-contrast: high) {
:focus-visible {
outline: 3px solid currentColor;
}
}检测工具使用
tsx
// Chrome DevTools 检测
// 1. 打开 DevTools
// 2. Elements 面板选择元素
// 3. Styles 面板点击颜色值
// 4. 查看 Contrast ratio
// Lighthouse 审计
// 1. DevTools > Lighthouse
// 2. 勾选 Accessibility
// 3. 运行审计
// axe DevTools
// 1. 安装 axe DevTools 扩展
// 2. DevTools > axe DevTools
// 3. 扫描页面暗色模式对比度
css
/* 暗色模式同样需要满足对比度要求 */
[data-theme="dark"] {
--background: #111827;
--foreground: #F9FAFB; /* on dark: 15.37:1 */
--muted: #9CA3AF; /* on dark: 7.35:1 */
/* 避免纯黑背景 + 纯白文字(对比度太高也不舒服) */
/* 避免深灰背景 + 浅灰文字(对比度不足) */
}AI 协作指南
核心意图:让 AI 帮你检查和优化颜色对比度。
需求定义公式:
- 检查内容:[配色方案/特定组件]
- 目标级别:WCAG [AA/AAA]
- 背景颜色:[亮色/暗色/都要]
示例 Prompt:
请检查我的配色方案是否满足 WCAG AA 标准:
背景: #FFFFFF
主要文字: #374151
次要文字: #9CA3AF
链接: #3B82F6
按钮: #2563EB (白色文字)
如果不满足,请提供替代颜色验收清单
- [ ] 文字对比度达到 4.5:1 (AA)
- [ ] 大字对比度达到 3:1
- [ ] 不只依赖颜色传达信息
- [ ] 焦点状态清晰可见
- [ ] 暗色模式对比度达标
