mcpField
D
dark-mode-design-expert

dark-mode-design-expert

github.com/curiositechデザイン・UI257304 回インストール2 日前に更新
リポジトリ

dark-mode-design-expert について

コントラスト、サーフェスの階層、テーマ切り替えを扱う、デザイナーと開発者向けのダークモード設計スキルです。天気、空、海を着想源とするカラーの例に加え、実装とテストの指針を提供します。

主な機能

  1. 01ダークモードのカラーシステム
  2. 02サーフェスの階層とトーン調整
  3. 03システム設定とテーマ切り替え
  4. 04天気と時刻に応じた配色
  5. 05WCAGコントラストの指針
  6. 06ビジュアル・機能テストのチェックリスト

活用シーン

  1. 01製品のダークテーマ設計
  2. 02ライト、ダーク、システムテーマの切り替え実装
  3. 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.json
references/
css-implementation.md
oled-optimization.md
elevation-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:17:1 以上読みやすさには AAA が望ましい
大きな文字(≥24px)3:14.5:1 以上見出し、ヒーローテキスト
UI コンポーネント3:14.5:1 以上境界線、アイコン、フォーカスリング
無効化された要素要件なし2.5:1UX 上の考慮事項
装飾要素要件なし-純粋に見た目だけの要素

ダークモードの注意点: コントラストが高い(黒に純白を重ねた 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#1212120%
1shadow-sm#1e1e1e白 5%
2shadow-md#232323白 7%
3shadow-lg#282828白 8%
4shadow-xl#2d2d2d白 9%
5shadow-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初期カラーパレットの生成

覚えておいてください。ダークモードは光がない状態ではありません。視線を導き、負担を軽減し、雰囲気をつくり出すために、輝度を注意深く調整することです。

コメント