チュートリアル

ダークモード実装:完全な開発者ガイド

2分で読める

ダークモードはニッチな優先から期待される機能に移動しました。すべてのプラットフォーム(macOS、Windows、Android、iOS)のユーザーはシステム全体のダークアピアランスを設定できます。彼らはウェブサイトとアプリがそれを尊重することを期待します。ダークモードを正しく実装することは、白を黒に交換する以上のことが必要です。色、コントラスト、ユーザーコントロールへの体系的なアプローチが必要です。このガイドは、CSSアーキテクチャからJavaScriptトグル機構まで、両方のテーマを徹底的にテストするまで、完全なプロセスを説明します。

テーマのためのCSSカスタムプロパティ

CSSでダークモードを処理するための最も保守可能な方法は、カスタムプロパティ(CSSカスタムプロパティとも呼ばれます)を使用することです。スタイルシート全体に色の値を散らす代わりに、:root上のすべての色を変数として定義し、ダークモード用にそれらの変数を再定義します。コンポーネントスタイルは、生のhex値ではなく、変数のみを参照します。

ライトおよびダークパレットの定義

ライトモードパレットをデフォルトとして開始してください。クリーンな出発点は次のようになります:

:root {
  /* バックグラウンド */
  --color-bg-base:      #FFFFFF;
  --color-bg-elevated:  #F8F9FA;
  --color-bg-overlay:   #F1F3F5;

  /* テキスト */
  --color-text-primary:   #1A1A2E;
  --color-text-secondary: #4A4A6A;
  --color-text-muted:     #6C757D;

  /* 枠線 */
  --color-border:         #DEE2E6;
  --color-border-strong:  #ADB5BD;

  /* ブランド / アクセント */
  --color-accent:         #3B82F6;
  --color-accent-hover:   #2563EB;

  /* フィードバック */
  --color-success:  #22C55E;
  --color-warning:  #F59E0B;
  --color-danger:   #EF4444;
}

次に、ダークモードのオーバーライドを別のブロックで定義してください。重要な洞察は、色を反転していないということです。代わりに、ダークサーフェス用に設計された異なる目的的なパレットを選択しています:

[data-theme="dark"] {
  /* バックグラウンド */
  --color-bg-base:      #0F0F17;
  --color-bg-elevated:  #1A1A2E;
  --color-bg-overlay:   #252540;

  /* テキスト */
  --color-text-primary:   #E8E8F0;
  --color-text-secondary: #A8A8C0;
  --color-text-muted:     #6A6A88;

  /* 枠線 */
  --color-border:         #2E2E4A;
  --color-border-strong:  #4A4A6A;

  /* ブランド / アクセント - しばしば暗いbg上の読み取り性のためにわずかに軽い */
  --color-accent:         #60A5FA;
  --color-accent-hover:   #93C5FD;

  /* フィードバック - 厳しさを避けるためにわずかに脱飽和 */
  --color-success:  #4ADE80;
  --color-warning:  #FCD34D;
  --color-danger:   #F87171;
}

ライトモードのアクセント#3B82F6がダークモードで#60A5FAになることに注意してください。色相は同じですが、明度は増加します。白いバックグラウンドに対してWCAG AAを渡す色は、調整しない限り、暗いに近いバックグラウンドに対してほぼ常に失敗します。シェードジェネレータを使用して、任意の色の完全な50~950範囲を探索します。各テーマに適切なシェードを選択するのは簡単になります。

コンポーネントで変数を使用する

パレットが確立されると、あらゆるコンポーネントは生の値ではなく変数を参照します:

.card {
  background-color: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  color: var(--color-text-primary);
}

.btn-primary {
  background-color: var(--color-accent);
  color: #FFFFFF;
}

.btn-primary:hover {
  background-color: var(--color-accent-hover);
}

[data-theme="dark"]属性が<html>要素に存在する場合、すべての変数は同時に更新され、それらを参照するあらゆるコンポーネントが外観を変更します。追加のCSSは必要ありません。

prefers-color-schemeメディアクエリ

ユーザーが切り替えと相互作用する前に、prefers-color-schemeメディアクエリを使用してオペレーティングシステム環境設定を尊重できます。このメディアクエリはOSがダークアピアランスに設定されているときに機能します。

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg-base:      #0F0F17;
    --color-bg-elevated:  #1A1A2E;
    --color-bg-overlay:   #252540;
    --color-text-primary:   #E8E8F0;
    --color-text-secondary: #A8A8C0;
    --color-text-muted:     #6A6A88;
    --color-border:         #2E2E4A;
    --color-border-strong:  #4A4A6A;
    --color-accent:         #60A5FA;
    --color-accent-hover:   #93C5FD;
    --color-success:  #4ADE80;
    --color-warning:  #FCD34D;
    --color-danger:   #F87171;
  }
}

このアプローチはJavaScriptなしで機能し、ゼロレイアウトシフトで、ページロードでユーザーの宣言された優先度を直ちに尊重します。これが正しいベースラインです。制限は、ユーザーがあなたのアプリケーションでそれをオーバーライドできないことです。OSがダークの場合、逃げ口なくサイトはダークです。これが、ほとんどの本番実装がメディアクエリの上部にJavaScriptトグルを層状化する理由です。

両方のアプローチを組み合わせる

推奨されるパターンは、メディアクエリをデフォルトとして使用し、data-theme属性を明示的なオーバーライドとして使用します。CSS仕様度のコツまたはルールを正しく順序付けることで、これを処理できます:

/* 1. ライトモードのデフォルト */
:root {
  --color-bg-base: #FFFFFF;
  /* ... */
}

/* 2. OSダークモードのオーバーライド(明示的な優先度が設定されていない場合) */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-bg-base: #0F0F17;
    /* ... */
  }
}

/* 3. 明示的なダークモード(ユーザーがJSを通じて切り替わった) */
[data-theme="dark"] {
  --color-bg-base: #0F0F17;
  /* ... */
}

メディアクエリの:not([data-theme="light"])セレクタは、ユーザーがライトモードを明示的に選択していない場合に限り、OS暗いいでが適用されることを意味します。一度切り替えると、彼らの明示的な選択が勝ちます。

JavaScriptでトグルメカニズム

ただし、実装されたトグルは3つのことを行います。すぐに現在の外観を変更し、localStorage内の優先度を永続化し、最初のペイント前にページロード時に保存された優先度を読み込みます。

ロード時の優先度の読み込み

このスクリプトは<head>内で実行する必要があります。ページがレンダリングされる前。不正なテーマのフラッシュを防ぐ:

<head>
  <script>
    (function() {
      const stored = localStorage.getItem('theme');
      const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
      const theme = stored ?? (prefersDark ? 'dark' : 'light');
      document.documentElement.setAttribute('data-theme', theme);
    })();
  </script>
</head>

これは、スタイルが適用される前に、<html>上にdata-themeを直ちに設定します。ブラウザは最初のペイントからカスタムプロパティ値を正しく計算します。フラッシュはありません。

トグル関数

function toggleTheme() {
  const current = document.documentElement.getAttribute('data-theme');
  const next = current === 'dark' ? 'light' : 'dark';
  document.documentElement.setAttribute('data-theme', next);
  localStorage.setItem('theme', next);
}

// ボタンにフックします
document.getElementById('theme-toggle').addEventListener('click', toggleTheme);

トグルボタン状態を同期する

トグルボタンは現在のモードを視覚的に反映する必要があります。シンプルなアプローチはアイコンを使用します:

<button id="theme-toggle" aria-label="ダークモードを切り替え">
  <span class="icon-light">☀️</span>
  <span class="icon-dark">🌙</span>
</button>
[data-theme="dark"] .icon-light { display: none; }
[data-theme="dark"] .icon-dark  { display: inline; }
[data-theme="light"] .icon-light { display: inline; }
[data-theme="light"] .icon-dark  { display: none; }

アイコン表示はdata-themeに関連付けられたCSS変数によって制御されているため、属性が変更されるたびにボタン状態が自動的に更新されます。追加のJavaScriptは必要ありません。

色適応戦略

ダークモード色を選択することは、ライトパレットを反転させるのと同じくらい簡単ではありません。いくつかの原則が良いダーク色の選択をガイドします。

コントラストを減らす、単に反転させない

純白を純黒上で(#FFFFFF#000000)は技術的には最大コントラスト、21:1ですが、拡張読み取りのために認識疲労です。両方の極端を減らします。オフホワイトの#E8E8F0を本文テキストに使用し、非常に暗いネイビーの#0F0F17をページバックグラウンドに使用してください。これは十分なコントラスト(依然として15:1以上)を保存しながら、視覚的疲労を軽減します。

コントラストチェッカーを使用して、ダークテーマ内のあらゆるテキストバックグラウンド組み合わせがWCAG AA以上(標準テキストの場合は4.5:1、大きなテキストの場合は3:1)を満たしていることを確認してください。一般的な失敗ポイントは以下です:

  • フォームフィールド内のプレースホルダーテキスト
  • 無効なボタンラベル
  • セカンダリメタデータテキスト(タイムスタンプ、バイライン)
  • 見える標識なしのアイコンのみのボタン

ダークサーフェスでの階層化されたエレベーション

ライトモードでは、エレベーションは通常、ドロップシャドウで表されます。ダークモードでは、影は暗いバックグラウンドに対して見えなくなります。Material Design 3の仕様は、より効果的なアプローチを導入しました。より軽いサーフェスがより高い感じ。昇降コンポーネント用にわずかに軽いバックグラウンドを使用します:

/* ダークモードのエレベーションスケール */
--color-bg-base:     #0F0F17;  /* ページバックグラウンド */
--color-bg-elevated: #1A1A2E;  /* カード、サイドバー */
--color-bg-overlay:  #252540;  /* モーダル、ドロップダウン */
--color-bg-tooltip:  #2E2E4A;  /* ツールチップ */

#0F0F17をベースとして、#1A1A2Eをカード用、#252540をモーダル用。各ステップはHSL明度用語で約8~10%軽くなっています。これは、影に依存することなく、明確な視覚階層を作成します。

ダークモード色をわずかに脱飽和

非常に飽和した色は、暗いバックグラウンドでネオンのようで厳しく見えます。ダークモード用のブランド色を適応させるときは、明度を増加させることの横で飽和度を10~20%減らします。活発な#22C55E成功緑の代わりに、#4ADE80を優先します。より軽く、わずかに低い飽和、眼圧なしで成功を読みます。

シェードジェネレータは理想的です。ブランドの主要な緑またはブルーを入力して、ダークモードのテキストとアイコン用の300~400範囲、インタラクティブ要素用の500~600範囲を探索します。

画像とメディア

白いバックグラウンドを持つ画像はダークモードで邪魔に見えます。CSSは役立つことができます:

/* ダークモードの画像の厳しさを減らす */
[data-theme="dark"] img:not([src*=".svg"]) {
  filter: brightness(0.9) contrast(1.05);
}

/* または、画像をバックグラウンドと微妙に混ぜます */
[data-theme="dark"] img {
  mix-blend-mode: luminosity;
  opacity: 0.9;
}

適応する必要があるSVGアイコンの場合、currentColorを塗りつぶし値として使用すると、自動的に現在のテキスト色を採用します:

.icon { color: var(--color-text-secondary); }
<svg fill="currentColor" viewBox="0 0 24 24">...</svg>

両方のモードをテストする

徹底的なテストは、暗いモード回帰が本番環境に滑り込まないようにします。

ブラウザDevToolsエミュレーション

ChromeとFirefoxはOSセッティングを変更することなく、DevToolsでダークモードエミュレーションを提供します。Chrome:DevToolsを開き、3ドットメニューをクリックして、[その他のツール→レンダリング]に移動し、[CSSメディア機能prefers-color-schemeをエミュレート]を「dark」に設定します。これにより、両方のモードを同時に比較できます。

自動コントラストテスト

手動スポットチェックはエラーが生じやすいです。自動コントラスト監査を開発ワークフローに統合します。AxeやLighthouseなどのツールをCIで使用して、WCAG閾値に失敗する新しい色の追加を捕捉します。コントラストチェッカーを使用して、フォアグラウンド/バックグラウンドのペアをすべてのWCAGレベルに対して迅速に確認できます。hex peringを貼り付けて、比率を即座に確認してください。

リアルコンテンツでテストする

ダークモードのバグは、しばしば動的コンテンツを持つページに表示されます:ユーザーアップロード画像、サードパーティ埋め込み、チャート、マップ。デザインシステムのコンポーネントライブラリだけではなく、現実的なコンテンツサンプルに対してテストしてください。

OSレベルテスト

DevToolsエミュレーション経由で検証した後、OSが実際にダークモードに設定されている場合にテストします。prefers-color-schemeメディアクエリはOSセッティングに基づいて機能し、設定が実数とエミュレートされているかに応じて、いくつかのブラウザが微妙に異なった動作をします。トランジションもテストします。ページが開いている間のモード切り替え、レイアウトシフトまたはレンダリングのアーティファクトが発生しないことを確認します。

一般的なピットフォールチェックリスト

  • コンポーネントCSS内のハードコードされたhex値。変数の代わりに。スタイルシートをスキャンして、生のhexコードを検索し、変数に置き換えます。
  • ハードコードされたfill="#000000"を持つSVGアイコン。fill="currentColor"に変更します。
  • data-themeを尊重しないサードパーティコンポーネント。スコープ付きCSSレイヤーでラップします。
  • color-schemeプロパティが設定されていません。color-scheme: light dark:rootに追加してください。
  • <head>から<meta name="color-scheme">がありません。ブラウザはCSSが読み込まれる前に正しいバックグラウンド色を適用できるようにします。
<meta name="color-scheme" content="light dark">
:root {
  color-scheme: light dark;
}

この小さな追加により、ネイティブスクロールバー、日付ピッカー、その他のOSレンダリングされたフォームコントロールは自動的に暗いバリアントに切り替わります。多くの実装が見落とされる詳細。

重要なポイント

  • CSSカスタムプロパティで:root上のすべての色を定義し、[data-theme="dark"]を使用してダークモードのそれらをオーバーライドします。コンポーネントスタイルは変数のみを参照し、テーマスイッチングをゼロ努力で行います。パレットが確立されたら。
  • ダークアピアランスにOSを設定したユーザーの自動デフォルトとしてprefers-color-scheme: darkを使用します。ユーザーがオーバーライドしたいユーザー用の上部にJavaScriptトグルをlocalStorage永続化で層状化します。
  • <head>でアンチフラッシュスクリプトを実行します。CSSが読み込まれる前。不正なテーマの最初ペイントフラッシュを防ぎます。
  • ダークモード色は反転されたライト色ではありません。極端なコントラストを減らし、エレベーション伝達に軽いバックグラウンドを使用し、ネオンの厳しさを避けるためにブランドアクセントをわずかに脱飽和します。
  • コントラストチェッカーを使用してあらゆるテキストバックグラウンドペアを検証し、シェードジェネレータを使用して、両方のテーマのブランドカラーの正しいシェードを見つけます。
  • color-scheme: light darkと対応する<meta>タグを追加して、ネイティブブラウザUI要素(スクロールバー、入力)も自動的に切り替わるようにします。

関連カラー

関連ブランド

関連ツール