コンポーネント固有のライトモードとダークモード

ウェブ機能: color-scheme light-dark()

color-scheme プロパティは、通常はルートに設定されますが、個々の要素に設定して、ページの他の部分とは異なるカラーパターンを強制的に適用することもできます。これは、特定のカラーパターン(常にダークモードまたはライトモードなど)で表示する必要があるコンポーネントに役立ちます。

たとえば、次のような場合があります。

  • デザイン上の理由から、ライトモードのページでもダークモードで表示されることが多い要素(コードブロック、メディア プレーヤー、フォト ギャラリーなど)
  • 明るい背景用に設計されたメディア(画像、動画、イラスト、印刷プレビューなど)を含む領域は、ページの残りの部分がダークモードであっても、ライトモードに設定できます。
  • コンポーネント プレビューなど、ユーザーレベルの設定でカラーパターンが制御される要素
  • ライトモードとダークモードの両方をサポートしていない埋め込み
  • デザインツール、地図、ビジュアリゼーション、ゲームなど。

色を変更する場合と、color-scheme を強制する場合

ライトモードで暗い背景に明るい色のテキストを使用する要素や、ダークモードで明るい背景に暗い色のテキストを使用する要素のすべてに、異なる color-scheme が必要になるわけではありません。たとえば、プライマリ ボタンはライトモードで青色に白色のテキストでレンダリングされることがありますが、color-scheme: dark は保証されません。

経験則として、通常、異なる color-scheme を使用する要素は、単純な浅いコンテナではなく、独自の視覚的コンテキストを確立する複雑なサーフェスです。

要素で別の color-scheme を使用することを検討する際は、次の点を自問してください。

  • カスタマイズされていない組み込みのブラウザ UI(フォーム コントロール、スクロールバーなど)は、そのカラーパターンを使用すべきか、それともページのカラーパターンに合わせるべきか。-> 前者の場合は、color-scheme を使用しないでください。
  • light-dark() の色は、ページの他の部分と同様に解決されるべきですか、それともオーバーライドに基づいて解決されるべきですか?-> 前者の場合は、color-scheme を使用しないでください。
  • 子孫は color-scheme に含めるべきでしょうか。そうでない場合は、color-scheme を使用しないでください。

基本的な実装

コンポーネント固有のオーバーライドは、通常(厳密には必要ありませんが)、グローバル color-scheme を介して複数のカラーパターンをサポートするページで使用されます。ページ全体のダークモードを適切に実装するには、ダークモードをご覧ください。

ページ全体の color-scheme が設定され、それに反応するカラー トークンを使用している場合(light-dark() など)、特定のコンポーネントに color-scheme を設定するだけで、そのサブツリーのカラーモードをオーバーライドできます。

pre, code, .dark {
  color-scheme: dark;
}

なお、一部のブラウザでは、コンポーネントが別のカラーパターンに自動的に適応されます。すべてのケースで指定されたカラーパターンを強制的に適用するには、only(color-scheme: dark; ではなく color-scheme: only dark;)を使用します。

色以外の値の適応

light-dark() は現在、色にのみ対応しています。

ベスト プラクティス

  • 必須: 背景のない要素に color-scheme を設定しないでください。異なるカラーパターンの背景とテキストの色の組み合わせが混在し、テキストが読めなくなる可能性があります。
  • 任意: ページ全体のダークモードと同じ色の組み合わせを再利用する方が簡単ですが、これらのコンポーネントに異なる色の組み合わせを定義することもできます。たとえば、ライトモードのページで使用されるダークモードのコンポーネントは、全体がダークモードのページで使用される同じダークモードのコンポーネントよりも少し明るくしたい場合があります。

注意が必要な既知の問題

重要な注意点: light-dark() の色の継承

light-dark() は、計算値の時点で解決されます。つまり、light-dark() 色に設定された継承された <color> プロパティは、light-dark() 式自体ではなく、2 つの色のうち 1 つのみを子孫に渡します。

これには以下が該当します。

  • 継承される組み込みの色プロパティ(color、accent-color、fill、stroke、text-shadow、caret-color など)
  • syntax: <color> と inherits: true を持つ登録済みの継承可能なカスタム プロパティ
  • inherit に設定されたその他の <color> プロパティ

このためには、次のことが必要になります。

  • デザイン トークン(--surface-color など)を保持するためのカスタム プロパティを <color> として登録しない。トークンは、子孫が別の color-scheme で再解決できるように、light-dark() 式をクイック掲載する必要があります。
  • 要素に color-scheme を設定する場合は、light-dark() 値に設定されている可能性がある継承された <color> プロパティを(直接またはデザイン トークンを介して)再指定します。これは、同じデザイン トークンに設定されている場合でも同様です。
  • color-scheme オーバーライドを持つ要素の <color> プロパティで inherit を使用しない(子孫で使用するのは問題ありません)。
  • 推奨: 登録済みの <color> プロパティは、逆のユースケースで使用します。つまり、祖先の解決済みの色を意図的にスナップショットし、子孫の color-scheme で再解決されないようにする場合です。たとえば、ページの背景をキャプチャして別の場所で使用する場合などです。
  • 色をアニメーション化する必要がある場合は、アニメーション化する要素に @property 登録済みの <color> プロパティを別途使用します(色の補間には登録が必要です)。これはデザイン トークンではなく、要素ごとのアニメーション ターゲットであるため、上記のルールと競合しません。

例:

:root {
    --accent-color: light-dark(blue, skyblue);
    --surface-color: light-dark(white, #222);
    --text-color: light-dark(#111, white);

    color-scheme: light dark;
    accent-color: var(--accent-color);
    color: var(--text-color);
}

body {
    /* --surface-color dynamically switches despite being inherited because --surface-color is not registered */
    background: var(--surface-color);
}

pre, code {
    color-scheme: dark;
    background: var(--surface-color);

    /* Without this, accent-color would be blue, not skyblue! */
    accent-color: var(--accent-color);

    /* Without this, text-color would be #111, not white! */
    color: var(--text-color);
}

color-scheme を使用する際の注意点

  • Chrome と Firefox は iframe の color-scheme を尊重します。埋め込みページを正しいカラーパターンでレンダリングし、埋め込みページの prefers-color-scheme メディアクエリを調整して、埋め込みコンテキストの color-scheme を反映します。Safari はそうではなく、iframe 内でも prefers-color-scheme をシステム設定に解決します。
    • 親と iframe の両方を制御する場合: iframe の作成時に URL パラメータ(?theme=dark)を介して、または postMessage()(ランタイムの変更に対応することも可能)を介して、親のカラーパターンを iframe に明示的に渡します。iframe で、prefers-color-scheme に依存するのではなく、そのシグナルから <html> にクラスを設定します(または :root に color-scheme を設定します)。
    • 埋め込みページのみを管理している場合: Safari の iframe 内から埋め込みコンテキストの color-scheme を検出する信頼性の高い方法はありません。埋め込み API(クエリ文字列や postMessage プロトコルなど)で明示的なテーマ パラメータを公開し、埋め込みユーザー向けにドキュメント化します。

代替戦略

color-scheme のフォールバックとブラウザ サポート

color-scheme プロパティはプログレッシブ エンハンスメントです。このプロパティをサポートしていないブラウザは、このプロパティを無視して、デフォルトのライトモード UI を使用します。

古いブラウザでユーザーの設定に適応するには、prefers-color-scheme メディアクエリを使用して、ダークモードが優先される場合に異なる色を提供します。

  • メディアクエリを使用して、:root または html でカスタム プロパティを切り替える
  • コンポーネントで色以外の非常に特殊なタイプのダークモードのカスタマイズが必要な場合を除き、個々のコンポーネントでメディアクエリを使用しないでください。
:root {
  /* Define brand colors for each mode */
  --color-brand-light: #0056b3;
  --color-brand-dark: #00e5ff;
  --color-brand: var(--color-brand-light);

  /* MANDATORY: Fallback for browsers without light-dark support */
  @media (prefers-color-scheme: dark) {
    --color-brand: var(--color-brand-dark);
  }

  /* Ignored in older browsers */
  color-scheme: light dark;
}

button.primary {
    background-color: var(--color-brand);
}

light-dark() のフォールバックとブラウザ サポート

color-scheme はサポートしているが light-dark() はまだサポートしていないブラウザの場合、色のライト バージョンとダーク バージョンをまずカスタム プロパティとして定義し、prefers-color-scheme メディアクエリを使用して、以下の例のようにそれぞれのモードの色を設定する必要があります。

:root {
  /* Define browser UI accent color for each mode */
  --brand-accent-light: #0056b3;
  --brand-accent-dark: #00e5ff;
  --accent-color: var(--brand-accent-light);

  /* MANDATORY: Fallback for browsers without light-dark support */
  @media (prefers-color-scheme: dark) {
    --accent-color: var(--brand-accent-dark);
  }

  /* OPTIONAL: use light-dark() for more control of built-in UI colors */
  @supports (color: light-dark(white, black)) {
    --accent-color: light-dark(var(--brand-accent-light), var(--brand-accent-dark));
  }

  /* MANDATORY: Automatically adapt native UI to user system preferences */
  color-scheme: light dark;

  /* Example inherited color property */
  accent-color: var(--accent-color);
}

pre, code {
  color-scheme: dark;

  /* **Mandatory**: any inherited color properties must be set again, even if to the same design tokens */
  accent-color: var(--accent-color);
}