关于 dark-mode-design-expert
面向设计师和开发者的深色模式 UI 设计技能,聚焦对比度、表面层级和主题切换。提供基于天气、天空与海洋氛围的配色令牌示例及实现、测试指导。
主要功能
- 01深色模式色彩系统
- 02表面层级与色调提升
- 03系统偏好与主题切换
- 04天气与时段氛围配色
- 05WCAG 对比度指导
- 06视觉与功能测试清单
适用场景
- 01为产品设计深色主题
- 02实现浅色、深色与系统主题切换
- 03检查暗色界面的对比度和层级
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ curiositech/ some_claude_skills --skill dark-mode-design-expert安装后在 Claude Code 中输入/dark-mode-design-expert确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.claude-plugin/plugin.jsonreferences/css-implementation.mdoled-optimization.mdelevation-strategies.md详细介绍
深色模式设计专家
精通深色模式 UI 设计,涵盖氛围主题、WCAG 无障碍要求和跨平台最佳实践。专注于受天气、天空和海洋启发的色彩系统,可根据一天中的时段和环境条件进行调整。
何时使用此技能
适用于:
- “dark mode”(深色模式)、“dark theme”(深色主题)、“night mode”(夜间模式)
- “theme switching”(主题切换)、“light/dark toggle”(浅色/深色切换)
- “atmospheric UI”(氛围 UI)、“weather theme”(天气主题)、“sky gradient”(天空渐变)
- “OLED optimization”(OLED 优化)、“battery-friendly dark”(省电深色模式)
- “elevation in dark mode”(深色模式中的高度层级)、“surface layering”(表面分层)
- “prefers-color-scheme”、 “color-scheme CSS”
- “contrast ratios dark mode”(深色模式对比度)、“accessibility dark theme”(无障碍深色主题)
不适用于:
- 通用色彩调色板设计 →
color-theory-palette-harmony-expert - 字体排版与字体选择 →
typography-expert - 组件库架构 →
design-system-creator - 特定颜色的对比度审查 →
color-contrast-auditor
深色模式的科学原理
深色模式的由来
| 因素 | 浅色模式 | 深色模式 | 更优者 |
|---|---|---|---|
| OLED 电池 | 100% 基准 | 最高亮度下可节省 39-47% | 深色 |
| 低光环境舒适度 | 眼睛疲劳、倦怠 | 减少眩光 | 深色 |
| 明亮环境 | 可读性更好 | 显得褪色 | 浅色 |
| 散光用户 | 更容易阅读 | 光晕效应 | 浅色 |
| 专注/沉浸感 | 常规 | 内容更突出 | 深色 |
| 睡眠卫生 | 暴露于蓝光 | 减少蓝光 | 深色 |
关键要点: 深色模式并非在所有情况下都更好,而是要根据情境判断。最佳系统应尊重用户偏好,并能适应环境。
对比度要求(WCAG 2.1)
| 元素类型 | 最低比例 | 目标比例 | 备注 |
|---|---|---|---|
| 正文 | 4.5:1 | 7:1+ | 为提升可读性,优先达到 AAA 级 |
| 大号文本(≥24px) | 3:1 | 4.5:1+ | 标题、主视觉文本 |
| UI 组件 | 3:1 | 4.5:1+ | 边框、图标、焦点环 |
| 禁用元素 | 无要求 | 2.5:1 | UX 考量 |
| 装饰元素 | 无要求 | - | 纯粹的美学元素 |
深色模式注意事项: 高对比度(纯黑背景上的纯白文字,21:1)比适度对比度(15:1)更容易造成眼睛疲劳。主要文本的目标对比度应为 12:1 至 16:1。
三层令牌架构
基础层:原始令牌 → 语义令牌 → 组件令牌
/* ══════════════════════════════════════════════════════════════════
TIER 1: PRIMITIVES - Raw color values, never used directly
══════════════════════════════════════════════════════════════════ */
:root {
/* Neutrals */
--color-gray-50: #f8fafc;
--color-gray-100: #f1f5f9;
--color-gray-200: #e2e8f0;
--color-gray-300: #cbd5e1;
--color-gray-400: #94a3b8;
--color-gray-500: #64748b;
--color-gray-600: #475569;
--color-gray-700: #334155;
--color-gray-800: #1e293b;
--color-gray-900: #0f172a;
--color-gray-950: #020617;
/* Brand Colors */
--color-ocean-300: #7dd3fc;
--color-ocean-400: #38bdf8;
--color-ocean-500: #0ea5e9;
--color-ocean-600: #0284c7;
--color-ocean-700: #0369a1;
/* Atmospheric Colors (for weather theming) */
--color-twilight-deep: #0c1222;
--color-twilight-mid: #151b2e;
--color-twilight-surface: #1a1f3a;
--color-dawn-warm: #fef3c7;
--color-sunset-orange: #fb923c;
--color-storm-gray: #374151;
}
/* ══════════════════════════════════════════════════════════════════
TIER 2: SEMANTIC - Purpose-driven, theme-aware
══════════════════════════════════════════════════════════════════ */
/* Light Mode (Default) */
:root, :root.theme-light {
/* Text */
--color-text-primary: var(--color-gray-900); /* 15.3:1 on white */
--color-text-secondary: var(--color-gray-600); /* 7.0:1 on white */
--color-text-muted: var(--color-gray-500); /* 4.6:1 on white */
--color-text-inverse: var(--color-gray-50);
/* Backgrounds */
--color-bg-primary: #ffffff;
--color-bg-secondary: var(--color-gray-50);
--color-bg-elevated: #ffffff;
--color-bg-overlay: rgba(0, 0, 0, 0.5);
/* Surfaces (elevation system) */
--color-surface-base: #ffffff;
--color-surface-raised: #ffffff;
--color-surface-overlay: #ffffff;
/* Borders */
--color-border-default: var(--color-gray-200);
--color-border-muted: var(--color-gray-100);
--color-border-emphasis: var(--color-gray-300);
/* Interactive */
--color-interactive-primary: var(--color-ocean-600);
--color-interactive-hover: var(--color-ocean-700);
--color-interactive-focus: var(--color-ocean-500);
/* Elevation (shadows work in light mode) */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15);
}
/* Dark Mode */
:root.theme-dark {
/* Text - slightly off-white to reduce strain */
--color-text-primary: var(--color-gray-50); /* 15.3:1 on dark */
--color-text-secondary: var(--color-gray-300); /* 9.3:1 on dark */
--color-text-muted: var(--color-gray-400); /* 5.5:1 on dark */
--color-text-inverse: var(--color-gray-900);
/* Backgrounds - NOT pure black (#000) */
--color-bg-primary: var(--color-twilight-deep); /* #0c1222 */
--color-bg-secondary: var(--color-twilight-mid); /* #151b2e */
--color-bg-elevated: var(--color-twilight-surface); /* #1a1f3a */
--color-bg-overlay: rgba(0, 0, 0, 0.7);
/* Surfaces - LIGHTER for elevation (key dark mode principle) */
--color-surface-base: var(--color-twilight-deep);
--color-surface-raised: var(--color-twilight-mid);
--color-surface-overlay: var(--color-twilight-surface);
/* Borders - more visible in dark mode */
--color-border-default: rgba(255, 255, 255, 0.1);
--color-border-muted: rgba(255, 255, 255, 0.05);
--color-border-emphasis: rgba(255, 255, 255, 0.2);
/* Interactive - brighter for visibility */
--color-interactive-primary: var(--color-ocean-400);
--color-interactive-hover: var(--color-ocean-300);
--color-interactive-focus: var(--color-ocean-500);
/* Elevation - GLOW replaces shadows in dark mode */
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.5);
--shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.5);
--shadow-xl: 0 12px 24px rgba(0, 0, 0, 0.6);
/* Glow effects (unique to dark mode) */
--glow-sm: 0 0 8px rgba(56, 189, 248, 0.2);
--glow-md: 0 0 16px rgba(56, 189, 248, 0.3);
--glow-lg: 0 0 32px rgba(56, 189, 248, 0.4);
}
/* ══════════════════════════════════════════════════════════════════
TIER 3: COMPONENT - Specific usage, consuming semantic tokens
══════════════════════════════════════════════════════════════════ */
:root {
/* Buttons */
--button-bg: var(--color-interactive-primary);
--button-text: var(--color-text-inverse);
--button-border: transparent;
--button-shadow: var(--shadow-sm);
/* Cards */
--card-bg: var(--color-surface-raised);
--card-border: var(--color-border-default);
--card-shadow: var(--shadow-md);
/* Inputs */
--input-bg: var(--color-bg-primary);
--input-border: var(--color-border-default);
--input-focus-ring: var(--color-interactive-focus);
}
深色模式中的高度层级:关键区别
为什么阴影在深色模式中失效
在浅色模式中,阴影通过模拟上方光源来营造深度。在深色模式中:
- 阴影在深色背景下会变得不可见
- 纯黑阴影看起来像“洞”
- 整体错觉会彻底失效
Material Design 3 解决方案:色调高度
不使用阴影,而是为浮起的元素使用更亮的表面颜色:
/* Light Mode: Shadows create elevation */
.card-light {
background: #ffffff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
/* Dark Mode: Surface color creates elevation */
.card-dark {
background: #1e1e1e; /* Elevated from #121212 base */
box-shadow: none; /* Or very subtle */
}
高度层级(Material Design 3)
| 级别 | 浅色模式 | 深色模式表面 | 覆盖层 % |
|---|---|---|---|
| 0(基底) | #ffffff | #121212 | 0% |
| 1 | shadow-sm | #1e1e1e | 5% 白色 |
| 2 | shadow-md | #232323 | 7% 白色 |
| 3 | shadow-lg | #282828 | 8% 白色 |
| 4 | shadow-xl | #2d2d2d | 9% 白色 |
| 5 | shadow-2xl | #323232 | 11% 白色 |
实现模式
:root.theme-dark {
/* Calculate overlay colors */
--elevation-1: color-mix(in srgb, white 5%, var(--color-bg-primary));
--elevation-2: color-mix(in srgb, white 7%, var(--color-bg-primary));
--elevation-3: color-mix(in srgb, white 8%, var(--color-bg-primary));
--elevation-4: color-mix(in srgb, white 9%, var(--color-bg-primary));
--elevation-5: color-mix(in srgb, white 11%, var(--color-bg-primary));
}
.card {
background: var(--elevation-2);
}
.modal {
background: var(--elevation-4);
}
.dropdown {
background: var(--elevation-3);
}
CSS 实现模式
现代方案:prefers-color-scheme + light-dark()
/* 1. Set color-scheme for native element styling */
:root {
color-scheme: light dark;
}
/* 2. Use light-dark() for inline theming (2024+ browsers) */
.card {
background: light-dark(#ffffff, #1e1e1e);
color: light-dark(#1f2937, #f3f4f6);
border: 1px solid light-dark(#e5e7eb, rgba(255,255,255,0.1));
}
/* 3. Respect system preference */
@media (prefers-color-scheme: dark) {
:root:not(.theme-light) {
/* Dark mode tokens */
}
}
@media (prefers-color-scheme: light) {
:root:not(.theme-dark) {
/* Light mode tokens */
}
}
使用 JavaScript 切换主题
// Theme manager with persistence
type Theme = 'light' | 'dark' | 'system';
function setTheme(theme: Theme) {
const root = document.documentElement;
// Remove existing theme classes
root.classList.remove('theme-light', 'theme-dark');
if (theme === 'system') {
// Let CSS media queries handle it
localStorage.removeItem('theme');
return;
}
// Apply explicit theme
root.classList.add(`theme-${theme}`);
localStorage.setItem('theme', theme);
}
function getSystemTheme(): 'light' | 'dark' {
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
}
// Initialize on page load (before render to prevent flash)
function initTheme() {
const saved = localStorage.getItem('theme') as Theme | null;
if (saved && saved !== 'system') {
document.documentElement.classList.add(`theme-${saved}`);
}
}
// Listen for system changes
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
// Only react if user hasn't set explicit preference
// CSS will handle via media queries
}
});
防止错误主题闪烁(FOWT)
<script>
(function() {
const theme = localStorage.getItem('theme');
if (theme === 'dark') {
document.documentElement.classList.add('theme-dark');
} else if (theme === 'light') {
document.documentElement.classList.add('theme-light');
}
})();
</script>
示例:天气/天空/海洋氛围 UI
这是旗舰示例——一套完整的令牌系统,适用于受天气启发、能根据一天中的时段和大气条件进行调整的 UI。
设计理念
海洋与天空共享一种视觉语言:
- 黎明/黄昏:温暖的渐变、柔和的过渡
- 正午:明亮、高对比度、清晰
- 夜晚:深蓝色、微妙的辉光、星星
- 风暴:戏剧性的灰色、电光般的高亮
- 水下:深邃的蓝绿色、生物荧光点缀
完整令牌系统
/* ══════════════════════════════════════════════════════════════════
OCEAN MODERN: ATMOSPHERIC UI TOKEN SYSTEM
A weather/sky/ocean-inspired design system with time-of-day awareness
══════════════════════════════════════════════════════════════════ */
:root {
/* ────────────────────────────────────────────────────────────────
PRIMITIVES: Atmospheric Color Palette
──────────────────────────────────────────────────────────────── */
/* Ocean Depths */
--ocean-surface: #38bdf8; /* Sunlit surface */
--ocean-shallow: #0ea5e9; /* Clear shallows */
--ocean-mid: #0284c7; /* Mid-depth */
--ocean-deep: #0369a1; /* Deep water */
--ocean-abyss: #075985; /* Abyssal zone */
--ocean-trench: #0c4a6e; /* Hadal zone */
/* Sky States */
--sky-dawn: #fef3c7; /* Golden hour */
--sky-morning: #bae6fd; /* Clear morning */
--sky-midday: #7dd3fc; /* Bright day */
--sky-golden: #fcd34d; /* Golden hour */
--sky-sunset: #fb923c; /* Sunset orange */
--sky-dusk: #a78bfa; /* Purple dusk */
--sky-twilight: #6366f1; /* Civil twilight */
--sky-night: #1e1b4b; /* Night sky */
/* Atmospheric Effects */
--atmosphere-haze: rgba(186, 230, 253, 0.3);
--atmosphere-fog: rgba(241, 245, 249, 0.6);
--atmosphere-mist: rgba(148, 163, 184, 0.4);
/* Storm System */
--storm-light: #9ca3af;
--storm-mid: #6b7280;
--storm-dark: #4b5563;
--storm-thunder: #374151;
--storm-lightning: #fbbf24;
/* Bioluminescence (dark mode accents) */
--bio-cyan: #22d3ee;
--bio-teal: #2dd4bf;
--bio-blue: #60a5fa;
--bio-purple: #a78bfa;
--bio-glow: rgba(34, 211, 238, 0.4);
/* Sand & Beach */
--sand-light: #fef3c7;
--sand-warm: #fde68a;
--sand-golden: #fcd34d;
--sand-wet: #d4a574;
/* Coral & Life */
--coral-pink: #fb7185;
--coral-orange: #fb923c;
--kelp-green: #22c55e;
--algae-teal: #14b8a6;
}
/* ════════════════════════════════════════════════════════════════════
SEMANTIC: Time-of-Day Themes
════════════════════════════════════════════════════════════════════ */
/* DAWN THEME (5am - 8am) - Warm, hopeful, transitional */
:root.atmosphere-dawn {
--color-text-primary: #1e293b;
--color-text-secondary: #475569;
--color-bg-primary: var(--sky-dawn);
--color-bg-secondary: #fef9e7;
--color-accent: var(--sky-golden);
--gradient-sky: linear-gradient(
180deg,
var(--sky-night) 0%,
var(--sky-dusk) 20%,
var(--sky-sunset) 40%,
var(--sky-golden) 70%,
var(--sky-dawn) 100%
);
--gradient-ocean: linear-gradient(
180deg,
var(--ocean-deep) 0%,
var(--ocean-mid) 50%,
var(--ocean-surface) 100%
);
}
/* DAYLIGHT THEME (8am - 5pm) - Bright, clear, energetic */
:root.atmosphere-day, :root.theme-light {
--color-text-primary: #0f172a;
--color-text-secondary: #334155;
--color-text-muted: #64748b;
--color-bg-primary: #ffffff;
--color-bg-secondary: #f8fafc;
--color-bg-elevated: #ffffff;
--color-accent: var(--ocean-shallow);
--color-accent-hover: var(--ocean-mid);
--gradient-sky: linear-gradient(
180deg,
var(--sky-midday) 0%,
var(--sky-morning) 50%,
#ffffff 100%
);
--gradient-ocean: linear-gradient(
180deg,
var(--ocean-abyss) 0%,
var(--ocean-deep) 30%,
var(--ocean-mid) 60%,
var(--ocean-shallow) 100%
);
/* Daylight uses shadows for elevation */
--elevation-method: shadow;
}
/* GOLDEN HOUR THEME (5pm - 7pm) - Warm, dramatic, nostalgic */
:root.atmosphere-golden {
--color-text-primary: #1c1917;
--color-text-secondary: #44403c;
--color-bg-primary: #fffbeb;
--color-bg-secondary: #fef3c7;
--color-accent: var(--sky-sunset);
--gradient-sky: linear-gradient(
180deg,
var(--sky-midday) 0%,
var(--sky-golden) 40%,
var(--sky-sunset) 70%,
var(--coral-pink) 100%
);
--gradient-ocean: linear-gradient(
180deg,
var(--ocean-deep) 0%,
#0891b2 50%,
#fcd34d 100%
);
}
/* TWILIGHT THEME (7pm - 9pm) - Transitional, mysterious */
:root.atmosphere-twilight {
--color-text-primary: #e2e8f0;
--color-text-secondary: #94a3b8;
--color-bg-primary: #0f172a;
--color-bg-secondary: #1e293b;
--color-bg-elevated: #334155;
--color-accent: var(--sky-twilight);
--gradient-sky: linear-gradient(
180deg,
var(--sky-night) 0%,
var(--sky-twilight) 30%,
var(--sky-dusk) 60%,
var(--sky-sunset) 100%
);
--gradient-ocean: linear-gradient(
180deg,
var(--ocean-trench) 0%,
var(--ocean-abyss) 50%,
var(--ocean-deep) 100%
);
}
/* NIGHT THEME (9pm - 5am) - Deep, calm, bioluminescent */
:root.atmosphere-night, :root.theme-dark {
--color-text-primary: #f1f5f9; /* 15.3:1 ✓ AAA */
--color-text-secondary: #cbd5e1; /* 9.3:1 ✓ AAA */
--color-text-muted: #94a3b8; /* 5.5:1 ✓ AA */
--color-bg-primary: #0c1222; /* Deep twilight navy */
--color-bg-secondary: #151b2e;
--color-bg-elevated: #1a1f3a;
--color-accent: var(--bio-cyan);
--color-accent-hover: var(--bio-teal);
--gradient-sky: linear-gradient(
180deg,
#020617 0%,
var(--sky-night) 50%,
#1e1b4b 100%
);
--gradient-ocean: linear-gradient(
180deg,
#020617 0%,
var(--ocean-trench) 50%,
var(--ocean-abyss) 100%
);
/* Night uses lighter surfaces for elevation */
--elevation-method: surface;
/* Bioluminescent glow effects */
--glow-accent: 0 0 20px var(--bio-glow);
--glow-subtle: 0 0 10px rgba(34, 211, 238, 0.2);
/* Surface elevation scale */
--surface-base: #0c1222;
--surface-1: #111827;
--surface-2: #1f2937;
--surface-3: #374151;
--surface-4: #4b5563;
}
/* STORM THEME - Dramatic, intense, electric */
:root.atmosphere-storm {
--color-text-primary: #f3f4f6;
--color-text-secondary: #d1d5db;
--color-bg-primary: var(--storm-thunder);
--color-bg-secondary: var(--storm-dark);
--color-bg-elevated: var(--storm-mid);
--color-accent: var(--storm-lightning);
--gradient-sky: linear-gradient(
180deg,
var(--storm-thunder) 0%,
var(--storm-dark) 30%,
var(--storm-mid) 60%,
var(--storm-light) 100%
);
--gradient-ocean: linear-gradient(
180deg,
#1f2937 0%,
#374151 40%,
#6b7280 80%,
#9ca3af 100%
);
/* Lightning flash animation */
--flash-color: rgba(251, 191, 36, 0.3);
}
/* ════════════════════════════════════════════════════════════════════
COMPONENT: Atmospheric UI Elements
════════════════════════════════════════════════════════════════════ */
/* Glass Card - works in all atmospheres */
.glass-card {
background: var(--glass-bg, rgba(255, 255, 255, 0.1));
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--glass-border, rgba(255, 255, 255, 0.2));
border-radius: 16px;
}
:root.theme-light .glass-card,
:root.atmosphere-day .glass-card,
:root.atmosphere-dawn .glass-card {
--glass-bg: rgba(255, 255, 255, 0.7);
--glass-border: rgba(0, 0, 0, 0.1);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
}
:root.theme-dark .glass-card,
:root.atmosphere-night .glass-card,
:root.atmosphere-twilight .glass-card {
--glass-bg: rgba(15, 23, 42, 0.6);
--glass-border: rgba(255, 255, 255, 0.1);
box-shadow: var(--glow-subtle);
}
/* Wave Animation */
.wave-layer {
position: absolute;
bottom: 0;
left: 0;
width: 200%;
height: var(--wave-height, 100px);
background: var(--wave-color);
animation: wave var(--wave-duration, 8s) ease-in-out infinite;
opacity: var(--wave-opacity, 0.6);
}
@keyframes wave {
0%, 100% { transform: translateX(0) translateY(0); }
50% { transform: translateX(-25%) translateY(-10px); }
}
/* Bioluminescent Glow (dark mode only) */
:root.theme-dark .glow-element,
:root.atmosphere-night .glow-element {
box-shadow: var(--glow-accent);
transition: box-shadow 0.3s ease;
}
:root.theme-dark .glow-element:hover,
:root.atmosphere-night .glow-element:hover {
box-shadow: 0 0 30px var(--bio-glow), 0 0 60px rgba(34, 211, 238, 0.2);
}
/* Cloud Layer */
.cloud-layer {
position: absolute;
width: 100%;
height: 100%;
background-image: var(--cloud-pattern);
opacity: var(--cloud-opacity, 0.5);
animation: drift var(--cloud-speed, 60s) linear infinite;
}
@keyframes drift {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
基于时间的主题切换
type Atmosphere = 'dawn' | 'day' | 'golden' | 'twilight' | 'night' | 'storm';
function getAtmosphereFromTime(hour: number): Atmosphere {
if (hour >= 5 && hour < 8) return 'dawn';
if (hour >= 8 && hour < 17) return 'day';
if (hour >= 17 && hour < 19) return 'golden';
if (hour >= 19 && hour < 21) return 'twilight';
return 'night';
}
function setAtmosphere(atmosphere: Atmosphere) {
const root = document.documentElement;
// Remove all atmosphere classes
root.classList.remove(
'atmosphere-dawn', 'atmosphere-day', 'atmosphere-golden',
'atmosphere-twilight', 'atmosphere-night', 'atmosphere-storm',
'theme-light', 'theme-dark'
);
// Add new atmosphere
root.classList.add(`atmosphere-${atmosphere}`);
// Also set base theme for compatibility
const isDark = ['twilight', 'night', 'storm'].includes(atmosphere);
root.classList.add(isDark ? 'theme-dark' : 'theme-light');
}
// Auto-update based on time
function initAtmosphericUI() {
const updateAtmosphere = () => {
const hour = new Date().getHours();
setAtmosphere(getAtmosphereFromTime(hour));
};
updateAtmosphere();
// Update every 15 minutes
setInterval(updateAtmosphere, 15 * 60 * 1000);
}
Weather API 集成
interface WeatherCondition {
main: 'Clear' | 'Clouds' | 'Rain' | 'Thunderstorm' | 'Snow' | 'Mist';
description: string;
}
function getAtmosphereFromWeather(
weather: WeatherCondition,
hour: number
): Atmosphere {
// Storm conditions override time
if (weather.main === 'Thunderstorm') return 'storm';
// Heavy overcast dims everything
if (weather.main === 'Clouds' && weather.description.includes('overcast')) {
return hour >= 19 || hour < 6 ? 'night' : 'twilight';
}
// Default to time-based
return getAtmosphereFromTime(hour);
}
应避免的反模式
1. 纯黑背景(#000000)
问题: 会导致眼疲劳、对比过于强烈,以及 OLED 屏幕滚动时出现“拖影”
解决方案: 使用接近黑色的颜色,例如 #0c1222、#121212 或 #1a1a2e
2. 深色背景上的纯白文字(#FFFFFF)
问题: 过于刺眼,会使散光用户产生光晕
解决方案: 使用柔白色,例如 #f1f5f9、#e2e8f0
3. 两种主题使用相同的颜色
问题: 在白色背景上效果很好的青绿色,在深色背景上会变得难以看清
解决方案: 在深色模式下使用更明亮的变体(使用 ocean-400,而不是 ocean-600)
4. 深色模式下使用阴影
问题: 阴影在深色背景上会消失
解决方案: 改用更浅的表面颜色来体现层级
5. 反转浅色模式的颜色
问题: 简单反转会产生刺眼、不自然的效果
解决方案: 将深色模式设计为独立且协调的系统
6. 忽略系统偏好设置
问题: 强制使用深色模式会忽略用户的系统级偏好设置
解决方案: 默认遵循 prefers-color-scheme,并允许用户覆盖
7. 错误主题闪现
问题: 页面先以浅色加载,然后闪变为深色
解决方案: 在 CSS 加载前,将内联脚本放在 <head> 中
测试清单
视觉测试
- 主文本在所有背景上都清晰易读(4.5:1+)
- 次要文本清晰易读(4.5:1+)
- 弱化文本的可读性可接受(大号文本为 3:1+,常规文本为 4.5:1+)
- 交互元素易于区分
- 焦点状态清晰可见
- 禁用状态易于辨认(但不要求满足对比度)
- 无需阴影即可清晰体现层级
- 不存在刺眼的纯白/纯黑组合
功能测试
- 主题切换功能正常
- 首次加载时遵循系统偏好设置
- 页面重新加载后主题保持不变
- 不会闪现错误主题
- 图片会相应调整
- 代码块清晰易读
- 图表保持清晰易读
- 表单元素样式正确
设备测试
- OLED 屏幕(检查滚动时是否出现拖影)
- LCD 屏幕(检查是否可见背光溢出)
- 高亮度户外使用
- 低亮度夜间使用
- 色盲模拟测试
行业参考
Material Design 3
- 基础深色表面:#121212
- 使用白色叠加层实现色调层级(5-11%)
- 深色模式下,主色亮度为 80%
- 提升表面的目标对比度为 15.8:1
Apple Human Interface Guidelines
- 遵循系统外观设置
- 使用可自动适配的语义颜色
- 深色模式下提高鲜艳度
- 基础系统背景:动态变化(而非固定为黑色)
Figma
- 背景:#2c2c2c(基础)、#383838(提升)
- 文本:#ffffff(主要)、#b3b3b3(次要)
- 强调色:#0d99ff(品牌蓝,在深色模式下提亮)
Discord
- 背景:#36393f(主区域)、#2f3136(侧边栏)
- 文本:#dcddde(主要)、#72767d(弱化)
- 强调色:#5865f2(蓝紫色,两种模式下相同)
Slack
- 背景:#1a1d21(基础)、#222529(提升)
- 深色模式下使用彩色侧边栏
- 在适配的同时保持品牌形象
配套技能
| 技能 | 交接节点 |
|---|---|
color-contrast-auditor | 设计好令牌后,审查具体的颜色组合 |
design-system-creator | 将深色模式融入更广泛的设计系统 |
web-design-expert | 确定整体视觉方向并与品牌保持一致 |
color-theory-palette-harmony-expert | 生成初始调色板 |
请记住:深色模式并非没有光,而是对亮度进行精心协调,以引导注意力、减轻视觉压力并营造氛围。