チュートリアル

CSS light-dark()関数:ネイティブテーマ切り替え

2分で読める

ダークモードの実装は歴史的に、非自明なボイラープレートの量を必要としました。すべてのカスタムプロパティを上書きするprefers-color-schemeメディアクエリ、ユーザートグルを処理するJavaScript、環境設定を永続化するlocalStorage、およびページロード時に間違ったテーマのフラッシュを防ぐ<head>内のインラインスクリプト。CSS light-dark()関数はこれすべてを排除しませんが、CSSサーフェース領域の問題を劇的に削減します。

light-dark()は、正確に2つのカラー値を取り、アクティブなカラースキームがライト、またはダークの場合に2番目のカラーが返される場合、最初を返すCSSカラー関数です。これはカラーの三項演算子の同等のセマンティックCSSです。

light-dark()とは何か

関数シグネチャは簡単です。

color: light-dark(<light-color>, <dark-color>);

アクティブなカラースキームがライトの場合、ブラウザは<light-color>を使用します。ダークの場合、<dark-color>を使用します。「アクティブなカラースキーム」は、color-schemeCSSプロパティによって決定されます。これは、システムprefers-color-schemeメディアクエリまたは要素に設定された明示的な値に対応しています。

関数は以下でサポートされています。

  • Chrome/Edge :バージョン123以来(2024年3月)
  • Firefox :バージョン120以来(2023年11月)
  • Safari :バージョン17.5以来(2024年6月)

グローバルサポートは2026年初頭に約85%です。これは比較的最近の追加ですが、ブラウザ範囲はフォールバック戦略を持つ本番環境で使用するのに十分に高速に成長しています。

color-schemeプロパティでの動作

light-dark()は分離では動作しません。color-schemeCSSプロパティが正しく設定されるかどうかに完全に依存しています。なしでは、関数は返すべき値を決定する文脈を持っていません。

color-schemeプロパティは、ドキュメントまたは要素がどのカラースキームをサポートするかを宣言します。:rootに設定することは開始点です。

:root {
  color-scheme: light dark;
}

この単一の宣言はブラウザに、ページがライトとダークの両方のカラースキームをサポートしていることを通知します。ブラウザは以下の通りです:

  1. ユーザーのprefers-color-schemeシステム優先度を読む
  2. 対応するスキームを適用
  3. ページのすべてのlight-dark()呼び出しを適切な値に解決

これが配置されたら、テーマ認識のカラーを定義することは、単一の宣言を書く問題になります。

:root {
  color-scheme: light dark;

  --color-background: light-dark(#FFFFFF, #0F0F17);
  --color-text:       light-dark(#1A1A2E, #E8E8F0);
  --color-border:     light-dark(#DEE2E6, #2E2E4A);
  --color-accent:     light-dark(#2563EB, #60A5FA);
}

メディアクエリなし。セレクタ上書きなし。各カラー1つの宣言、両方の値が行内。ブラウザはシステム環境設定に基づいて切り替えを自動的に処理します。

単一スキームへの制限

color-scheme: lightまたはcolor-scheme: darkを設定すると、システム環境設定に関係なく、単一スキームが強制されます。

/* OS環境設定に関係なく常にライト */
.widget {
  color-scheme: light;
  background: light-dark(#FFFFFF, #0F0F17);
  /* 常に#FFFFFFに解決 */
}

/* 常にダーク */
.dark-panel {
  color-scheme: dark;
  color: light-dark(#1A1A2E, #E8E8F0);
  /* 常に#E8E8F0に解決 */
}

これはUIコンポーネントが常に特定のモードに現れるべきである場合に有用です。例えば、周囲のページテーマに関係なく常にダーク背景を持つべきコードエディタ。

onlyキーワード

onlyを追加することは、カスケードがその要素のスキームを上書きすることを防ぎます。

.forced-light {
  color-scheme: only light;
}

これは主に、ダークモード文脈内にあり、軽い状態にとどまる必要がある要素がある場合に有用です。

prefers-color-schemeメディアクエリを置き換える

メディアクエリを使用した従来のダークモードアプローチは、すべてのカラー変数を複製またはオーバーライドする必要があります。

/* 従来のアプローチ — 詳細 */
:root {
  --bg: #FFFFFF;
  --text: #1A1A2E;
  --accent: #2563EB;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0F0F17;
    --text: #E8E8F0;
    --accent: #60A5FA;
  }
}

light-dark()で、これは以下に崩壊します。

/* light-dark()アプローチ — 変数あたり1つの宣言 */
:root {
  color-scheme: light dark;

  --bg:     light-dark(#FFFFFF, #0F0F17);
  --text:   light-dark(#1A1A2E, #E8E8F0);
  --accent: light-dark(#2563EB, #60A5FA);
}

変数自体は自らを説明する。--accent: light-dark(#2563EB, #60A5FA)を読む場合、両方の値が即座に表示され、関係が理解されます。メディアクエリアプローチは、光とダーク値を2つの個別ブロック全体に散在させ、パレットの監査と更新を困難にします。

メディアクエリがまだ必要な場合

prefers-color-schemeメディアクエリは、light-dark()が表現できないテーマに基づいて変わる非カラー適応に必要です。

@media (prefers-color-scheme: dark) {
  /* light-dark()が表現できない非カラー調整 */
  img.logo {
    filter: invert(1) brightness(1.2);
  }

  .hero-image {
    opacity: 0.85;
  }
}

純粋にカラー変更であるもの、light-dark()はよりクリーンです。構造的または非カラー適応(画像フィルタ、不透明度、ディスプレイプロパティ)については、メディアクエリは正しいツールのままです。

CSSカスタムプロパティとの組み合わせ

light-dark()はカスタムプロパティ値内で機能し、ここで完全な力が浮上しています。すべてのテーマ認識のカラーを:rootで定義し、ページのすべてのコンポーネントはこれらの変数を参照します。カラースキームが変更されると、すべてが同時に更新されます。

フルテーマシステム例

:root {
  color-scheme: light dark;

  /* 背景 */
  --color-bg-base:      light-dark(#FFFFFF,   #0F0F17);
  --color-bg-elevated:  light-dark(#F8F9FA,   #1A1A2E);
  --color-bg-overlay:   light-dark(#F1F3F5,   #252540);

  /* テキスト */
  --color-text-primary:   light-dark(#1A1A2E, #E8E8F0);
  --color-text-secondary: light-dark(#4A4A6A, #A8A8C0);
  --color-text-muted:     light-dark(#6C757D, #6A6A88);
  --color-text-inverse:   light-dark(#FFFFFF, #1A1A2E);

  /* ボーダー */
  --color-border:         light-dark(#DEE2E6, #2E2E4A);
  --color-border-strong:  light-dark(#ADB5BD, #4A4A6A);

  /* インタラクティブ/ブランド */
  --color-accent:         light-dark(#2563EB, #60A5FA);
  --color-accent-hover:   light-dark(#1D4ED8, #93C5FD);
  --color-accent-subtle:  light-dark(#DBEAFE, #1E3A5F);

  /* フィードバック */
  --color-success:  light-dark(#16A34A, #4ADE80);
  --color-warning:  light-dark(#D97706, #FCD34D);
  --color-danger:   light-dark(#DC2626, #F87171);

  --color-success-bg: light-dark(#F0FDF4, #052E16);
  --color-warning-bg: light-dark(#FFFBEB, #2D1A00);
  --color-danger-bg:  light-dark(#FEF2F2, #2D0A0A);
}

コンポーネントはテーマについて何かを知る必要なく、これらの変数を参照します。

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

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

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

.alert-success {
  background: var(--color-success-bg);
  color: var(--color-success);
  border-left: 3px solid var(--color-success);
}

ライトアクセント#2563EBがダークモードで#60A5FAに変わることに注意してください。これは意図的です。600重ブルーは白に対するWCAG AAコントラストを通過しますが、ダーク背景に対して失敗します。400重はダークサーフェスで許容できるコントラストを維持するのに十分にカラーをライトにします。コントラストチェッカーを使用して各組み合わせが目標コントラスト比を満たすことを確認し、シェードジェネレータを使用して各モード用の各カラーの正しいシェードを見つけます。

他の関数内のlight-dark()ネスト

light-dark()はカラー値を返すため、カラーが有効な場所——他の関数内を含む。

:root {
  color-scheme: light dark;
  --brand: #3B82F6;

  /* color-mix()内でlight-dark()を使用 */
  --brand-surface: color-mix(
    in oklch,
    var(--brand) 15%,
    light-dark(white, #09090b)
  );
}

これは、ブランドカラーの15%ティントであるサーフェスカラーを作成し、ライトモードの白でミックスし、ダークモードでニアブラック — 自動的にテーマ認識。

ユーザートグル(JavaScript)の追加

システム環境設定に応答することは正しいデフォルトですが、ユーザーはそれをオーバーライドできるべきです。これは、彼らの選択を永続化し、ブラウザのデフォルトをオーバーライドするJavaScriptを必要とします。

JavaScriptでcolor-schemeを制御する

重要な洞察は、color-schemeはJavaScriptを通じて設定できるCSSプロパティです。

// プログラム的にカラースキームを設定
document.documentElement.style.colorScheme = 'dark';
document.documentElement.style.colorScheme = 'light';

// オーバーライドを削除(システム優先に戻す)
document.documentElement.style.colorScheme = '';

ルート要素でcolor-schemeをインラインスタイルで設定する場合、それはスタイルシート宣言をオーバーライドします。すべてのlight-dark()値は適切なバリアントに再解決します。

完全なトグル実装

const STORAGE_KEY = 'color-scheme-preference';

function initColorScheme() {
  const stored = localStorage.getItem(STORAGE_KEY);
  if (stored === 'light' || stored === 'dark') {
    document.documentElement.style.colorScheme = stored;
  }
  // 保存された環境設定がない場合、CSS color-scheme: light dark; OS優先度を通じて処理
}

function toggleColorScheme() {
  const current = getComputedStyle(document.documentElement)
    .colorScheme
    .trim();

  // 次の値を決定
  const next = current.includes('dark') ? 'light' : 'dark';

  document.documentElement.style.colorScheme = next;
  localStorage.setItem(STORAGE_KEY, next);

  // 任意のトグルボタン状態を更新
  updateToggleButton(next);
}

function updateToggleButton(scheme) {
  const btn = document.getElementById('theme-toggle');
  if (!btn) return;
  btn.setAttribute('aria-label',
    scheme === 'dark' ? 'ライトモードに切り替え' : 'ダークモードに切り替え'
  );
  btn.dataset.scheme = scheme;
}

// 最初のペイント前に実行、フラッシュを回避
initColorScheme();

// DOMが準備できた後、トグルボタンに接続
document.addEventListener('DOMContentLoaded', () => {
  document.getElementById('theme-toggle')
    ?.addEventListener('click', toggleColorScheme);
});

initColorScheme()をDOMが完全に解析される前に呼び出すことは重要です。遅く実行する場合、保存された環境設定を持つユーザーは、切り替える前に、OS既定のテーマを簡潔に表示します。これは古典的な「間違ったテーマのフラッシュ」です。このスクリプトを<head>にインラインで配置するか、defer属性を注意深く使用します(注:deferスクリプトはDOM解析後に実行され、それは遅すぎるかもしれません)。

アンチフラッシュパターン

最も堅牢なアンチフラッシュアプローチは、<head>の最小インラインスクリプトを実行します。

<head>
  <meta name="color-scheme" content="light dark">
  <script>
    const stored = localStorage.getItem('color-scheme-preference');
    if (stored) {
      document.documentElement.style.colorScheme = stored;
    }
  </script>
  <link rel="stylesheet" href="styles.css">
</head>

このスクリプトはCSSが適用される前に同期的に実行されるため、ブラウザは最初のペイントから正しいcolor-scheme値を計算します。<meta name="color-scheme">タグはブラウザにCSSが解析されても期待するスキームを通知します。これは一部のブラウザでダークモードページの簡潔な白いフラッシュを防ぎます。

JSベースのテーマからの移行ガイド

多くの既存のダークモード実装は、JavaScriptで切り替えられたdata-theme属性を使用し、[data-theme="dark"]にスコープされたCSSオーバーライド。light-dark()への移行は増分です。一度にすべてを変更する必要はありません。

ステップ1::rootにcolor-schemeを追加

:root {
  color-scheme: light dark;
  /* 既存のカスタムプロパティは変更されないままです */
}

ステップ2:1つずつ変数を移行

概念実証として単一の変数から始めます。分割宣言パターンを統一light-dark()に置き換えます。

/* 前に */
:root {
  --bg: #FFFFFF;
}
[data-theme="dark"] {
  --bg: #0F0F17;
}

/* 後に */
:root {
  color-scheme: light dark;
  --bg: light-dark(#FFFFFF, #0F0F17);
}

ステップ3:トグルを更新

data-themeを設定してからstyle.colorSchemeを設定にJavaScriptトグルを変更します。

/* 前に */
document.documentElement.setAttribute('data-theme', scheme);

/* 後に */
document.documentElement.style.colorScheme = scheme;

ステップ4:data-themeセレクタを削除

すべての変数が移行されると、[data-theme="dark"]CSSブロックを削除します。

遷移中に両方を保持

両方のシステムを同時に実行できます。[data-theme="dark"]オーバーライドをまだ移行されていない任意の変数に保持します。新しい変数はlight-dark()を使用します。JavaScriptトグルは、遷移期間中にdata-themestyle.colorSchemeの両方を設定します。

ブラウザサポートフォールバック

light-dark()サポートなしブラウザの約15%について、明示的なフォールバックを提供します。

:root {
  /* フォールバック:明示的なライトモード値 */
  --color-bg: #FFFFFF;
  --color-text: #1A1A2E;

  /* light-dark()による段階的な強化 */
  --color-bg:   light-dark(#FFFFFF, #0F0F17);
  --color-text: light-dark(#1A1A2E, #E8E8F0);
}

/* light-dark()なしブラウザ用フォールバックダークモード */
@supports not (color: light-dark(white, black)) {
  @media (prefers-color-scheme: dark) {
    :root {
      --color-bg: #0F0F17;
      --color-text: #E8E8F0;
    }
  }
}

@supports not (color: light-dark(white, black))ブロックは、light-dark()を理解しないブラウザにのみ適用されます。モダンブラウザは、否定条件が偽であるため、完全にスキップします。

主要なポイント

  • light-dark(<light-value>, <dark-value>)はライトカラースキームで最初の引数を返し、ダークスキームで2番目を返します。これは「このカラー、現在のテーマに適応」を表現するCSS本来の方法です。
  • それは要素(または祖先)に設定されているcolor-schemeCSSプロパティに依存しています。常に:rootcolor-scheme: light darkを設定して、prefers-color-schemeを通じた自動適応を有効にします。
  • 従来のメディアクエリアプローチに比べた主な利点は、両方のテーマ値を単一の宣言に共線することです。ライト変異とダーク変異間の関係を明示的で監査可能にします。
  • ユーザーのオーバーライドは、JavaScriptを通じてdocument.documentElement.style.colorSchemeを設定する必要があります。localStorageの選択を永続化し、CSSロード前に<head>スクリプトにインラインで適用して、フラッシュを防ぎます。
  • data-theme属性ベースシステムからの移行は増分です。[data-theme="dark"]オーバーライドパターンからlight-dark()インラインパターンへ、1つずつ移動します。
  • ブラウザサポートは2026年時点で~85%です。古い環境の場合、@media (prefers-color-scheme: dark)ブロック付き@supports notフォールバックを提供します。
  • コントラストチェッカーを使用して、各light-dark()ペアのライトダークカラー値の両方が、それぞれの背景に対するWCAGコントラスト要件を通過することを確認し、シェードジェネレータを使用して、各モード用の各カラーの正しいシェードを見つけます。

関連カラー

関連ブランド

関連ツール