dark-mode-design-expert について
コントラスト、サーフェスの階層、テーマ切り替えを扱う、デザイナーと開発者向けのダークモード設計スキルです。天気、空、海を着想源とするカラーの例に加え、実装とテストの指針を提供します。
主な機能
- 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詳細
ダークモードデザインのエキスパート
雰囲気のあるテーマ設定、WCAG アクセシビリティ、クロスプラットフォームのベストプラクティスを取り入れた、ダークモード UI デザインを習得します。時間帯や環境条件に応じて変化する、天気・空・海をイメージしたカラーシステムを専門としています。
このスキルを使う場面
以下の場合に有効にします:
- 「ダークモード」「ダークテーマ」「ナイトモード」
- 「テーマ切り替え」「ライト/ダーク切り替え」
- 「雰囲気のある UI」「天気テーマ」「空のグラデーション」
- 「OLED 最適化」「バッテリーに優しいダークモード」
- 「ダークモードでのエレベーション」「サーフェスのレイヤー化」
- 「prefers-color-scheme」「color-scheme CSS」
- 「ダークモードのコントラスト比」「アクセシブルなダークテーマ」
以下の場合は対象外です:
- 一般的なカラーパレットの作成 →
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を目標にしてください。
3 層のトークンアーキテクチャ
基盤:プリミティブ → セマンティック → コンポーネント
/* ══════════════════════════════════════════════════════════════════
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-600 ではなく ocean-400)
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 | 初期カラーパレットの生成 |
覚えておいてください。ダークモードは光がない状態ではありません。視線を導き、負担を軽減し、雰囲気をつくり出すために、輝度を注意深く調整することです。