2024 年の Webサイトダークモード対応

## はじめに
この記事は「NTTテクノクロス Advent Calendar 2024（シリーズ2）」の3日目の記事です。

こんにちは。NTT テクノクロスの鈴木雅貴です。社内のあれこれをデザインの力でよくしていくというチームのリーダをしています。個人としては Web 標準技術まわりのスキルで生きており、[HTML5 プロフェッショナル認定試験 レベル 1 対策テキスト＆問題集　 Ver2.5 対応版](https://book.mynavi.jp/ec/products/detail/id=132028)のいち著者をしています。

今回ですが、2023 年度のアドベントカレンダーで書いたダークモード記事[「CSS の light-dark()で Web サイトを楽にダークモード対応させられそう」](https://qiita.com/suzukima/items/b9fccc00efb89e1bb997)」記事から状況に変化があったので、そこを踏まえていまダークモード対応するならこう！という話と、あわせて`color-scheme`に少し踏み込んで調べた話をします。

## 事前のおことわり

今回紹介する機能は、2024 年 12 月 1 日現在の情報を元にしています。

## 2024 年度のダークモード対応はこう！

前回記事の紹介どおりなのですが、`color-scheme`プロパティと`light-dark()`関数を使いましょう。

例えば、通常(以降ライトモードと書きます)は背景色が白で文字色が黒、ダークモードでは逆にしたい場合は、このように書きます。

```css
:root {
  color-scheme: light dark; /* カラースキームとしてライトモード用のlightとダークモード用のdarkを使う */
  --text-color: #222; /* ライトモードの文字色 */
  --background-color: #f4f4f4; /* ライトモードの背景色 */
}

body {
  /* light-dark(A, B) とすると、ライトモードではA、ダークモードではBの色が使われる */
  /* ライトモードとダークモードで文字色と背景色が入れ替わる */
  color: light-dark(var(--text-color), var(--background-color));
  background-color: light-dark(var(--background-color), var(--text-color));
}
```

こうすると、ライトモードに設定された Web ブラウザでは次のようになります。

![ライトモード表示のキャプチャ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16939/d36dd4ed-f0f3-afbb-8528-24e34f977c18.png)

ダークモードに設定された Web ブラウザでは背景色と文字色が入れ替わり、

![ダークモード表示のキャプチャ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16939/30fbf39f-d421-d827-f94f-19b17c06c4bf.png)

となります。簡単にできますね！

### Web ブラウザの対応状況

前回記事を執筆した 2023 年 12 月時点から状況が変わり、すべてのモダンブラウザが`color-scheme`と`light-dark()`に対応しています。もう安心して使えますね！

- 参考 1: [CSS property: color-scheme | Can I use... Support tables for HTML5, CSS3, etc](https://caniuse.com/mdn-css_properties_color-scheme)
- 参考 2: [types: `<color>`: `light-dark()` \| Can I use... Support tables for HTML5, CSS3, etc](https://caniuse.com/mdn-css_types_color_light-dark)

### Web ブラウザのダークモード表示確認方法

Web ブラウザ側でも比較的簡単にダークモードの表示を確かめられるようになりました。

#### Google Chrome・Microsoft Edge の場合

DevTools を開いた状態で Ctrl+Shift+P(macOS は command+Shift+P)を押すと、コマンドメニューが表示されます。

![Google Chromeのコマンドメニューを表示した状態](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16939/90f45b38-fcb7-ba23-1508-5c06c0aa637b.png)

そこで`prefers-color`と入力すると、以下の候補が表示されます。そこから実施したいものを選択してください。

|選択肢|実施されるもの|
|--|--|
|CSS prefers-color-scheme: dark をエミュレートする|ダークモードでの表示確認|
|CSS prefers-color-scheme: dark をエミュレートする|ライトモードでの表示確認|
|CSS prefers-color-scheme をトグル|ライトモードとダークモードをトグルする|

#### Mozilla Firefox の場合

前回記事と変わらず、DevTools の「インスペクター」を選んだ状態で、以下画像右上、ピンクで囲った箇所の三日月型のアイコンをクリックするとダークモード、太陽型のアイコンをクリックするとライトモードの表示を確認することができます。

![Mozilla Firefoxでのモード指定箇所](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16939/25ee2849-b365-e385-ebb8-a64afcb728a5.png)

#### Safari の場合

DevTools にて「ユーザー環境設定を上書き」のアイコンを選ぶと、外観のカラースキームを選べるツールチップが表示されます。カラースキームの設定を「ライト」にするとライトモード、「ダーク」にするとダークモードの表示を確認することができます。

![Safariでのモード指定箇所](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16939/3cf894bc-4018-455e-72f2-5ad7150be8f8.png)

1 年ですっかり環境が整いましたね！

## `color-scheme`は何をやっているのか

前述したように、`light-dark`と`color-scheme`を使うと簡単にダークモード対応できるわけですが、`@media`と`prefers-color-scheme`でダークモード対応していたのと、一体何が違うのでしょうか。気になりましたので、仕様を調べてみました。

### `@media`と`prefers-color-scheme`はユーザーの要求ベース

以下は、`@media`と`prefers-color-scheme`を使ってダークモード対応している例です。

```css
@media (prefers-color-scheme: dark) {
  /* ダークモード時のCSSをここに書く */
}
```

`prefers-color-scheme`はユーザー(Web ブラウザ側)がどういうモードを要求しているかの値です。ライトモードが要求されていれば`light`、ダークモードでは`dark`になります。ユーザーからの要求を見て、それが`dark`、すなわちダークモードであった場合の CSS を設定しているわけですね。

つまり、`@media`と`prefers-color-scheme`を使った方法は、ユーザーの要求を見て、それに従いスタイルを指定しているわけです。

### `color-scheme`は Web ページからの主張

で、`color-scheme`は何をしているかというと、Web ページの側からサポートするカラースキーム(ライトモードかダークモードのどちらか)を主張します。こんな感じです。

```css
/* デフォルトは何もサポートしないことになっている(のでブラウザのデフォルトテーマになる) */
color-scheme: normal;

/* ライトモードのみサポートする */
color-scheme: light;

/* ダークモードのみサポートする */
color-scheme: dark;

/* ライトモードとダークモードの両方をサポートする  */
color-scheme: light dark;
```

そして、`light-dark()`でライトモードとダークモードの色を指定します。あとはユーザーからの要求がサポートを主張したモードに含まれていれば、そのモードで設定した色を使う、という仕組みになっています。

### ユーザーの要求と Web ページの主張を調整する

この仕組みだと、ユーザーの要求と Web ページの主張が食い違う場合がありえます。このユーザー側の要求(`prefers-color-scheme`)と、Web ページ側の主張(`color-scheme`)は次のように調整され、モードが決まります。

|                          | prefers-color-scheme の値が light | prefers-color-scheme の値が dark |
| ------------------------ | --------------------------------- | -------------------------------- |
| color-scheme: normal     | ブラウザのデフォルトテーマ        | ブラウザのデフォルトテーマ       |
| color-scheme: light      | ライトモード                      | ライトモード                     |
| color-scheme: dark       | ダークモード                      | ダークモード                     |
| color-scheme: light dark | ライトモード                      | ダークモード                     |

ライトモードとダークモードを切り替えられるようにしておきたい場合は、`color-scheme: light dark`としておけばいいわけです。

ただこれだと、`color-scheme: light` となっていた場合、ユーザーはダークモードを使うことができません。それでは困る場合もありますので、ユーザーからモードを強制することができるようになっています。例えば Google Chrome では、DevTools から「自動ダークモードを有効にする」ことで、ダークモードを強制することができます。

DevTools の右上三点リーダーから、「その他のツール」→「レンダリング」を選び、

![Google Chromeでのダークモード強制設定手順その1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16939/80d1bc4a-8031-6102-ac7e-2eb0be3c3f10.png)

そこから「自動ダークモードを有効にする」を選択すれば、ダークモードを強制できます。

![Google Chromeでのダークモード強制設定手順その2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16939/1e9486d2-a73a-12e2-fee9-84e27d711780.png)

このように、Web ブラウザ側からモード強制があったときには、調整の結果が次の表のように変わります。

|                          | prefers-color-scheme の値が light(強制) | prefers-color-scheme の値が dark(強制) |
| ------------------------ | --------------------------------------- | -------------------------------------- |
| color-scheme: normal     | **ライトモード(強制)**                  | **ダークモード(強制)**                 |
| color-scheme: light      | ライトモード                            | **ダークモード(強制)**                 |
| color-scheme: dark       | **ライトモード(強制)**                  | ダークモード                           |
| color-scheme: light dark | ライトモード                            | ダークモード                           |

このように、Web ブラウザ側から、`color-scheme`の設定にかかわらずモードを強制できる仕組みが用意されています。

が、実は`color-scheme`側でこのモード強制を拒否することもできるようになっています。それが`only`キーワードです。

```css
/* 絶対にダークモードのみ対応 */
color-scheme: only dark;
```

`only`がついたカラースキーマは強制を無視します。ので、このように指定されたページでは、ダークモードしか受け付けません。

このあたりの調整の仕組みを把握しておくと、思い通りにモードが変わらないときにも焦らなくてすみそうです。

## 前回記事訂正

前回記事で`color-scheme`の記述に誤りがありましたので、こちらで修正します。(前回記事もあわせて修正しています)

`color-scheme`では、Web ページでサポートするカラースキームを宣言することができます。その旨を前回記事でも書いていたのですが、

> ここへ新たに別のカラースキームを追加することもできます。hoge を追加するなら color-scheme: light dark hoge;みたいに。

ここの部分が誤りで、任意のカラースキームを追加して宣言することはできません。

[CSS Color Adjustment Module Level 1 の color-scheme のところ](https://www.w3.org/TR/css-color-adjust-1/#color-scheme-prop) によりますと、取りうる値は`normal`, `light`, `dark`, `<custom-ident>`(カスタム値), あとはこれらに`only`をつけたものだとあります。カスタム値があるのですが、下の解説にこうあります。

> <custom-ident> values are meaningless, and exist only for future compatibility, so that future added color schemes do not invalidate the color-scheme declaration in legacy user agents. User agents must not interpret any <custom-ident> values as having a meaning; any additional recognized color schemes must be explicitly added to this property’s grammar.
> (参考の日本語訳) <custom-ident> の値は意味がなく、将来の互換性のためだけに存在する。よって、将来追加される color-scheme よって、従来のユーザーエージェントの color-scheme 宣言が無効になることはない。ユーザーエージェントは<custom-ident>を意味あるものとして解釈してはいけない。追加で認識された color-scheme は、このプロパティの文法に明示的に追加される必要がある。

`<custom-ident>`は意味がないし、あたらしく追加するなら文法レベルでいれろとあります。ということで、スキームを定義できるという理解は誤りです。失礼いたしました。

## おわりに

2024 年 12 月現在のダークモード対応方法として、`color-scheme`と`light-dark()`を使った方法を紹介しました。すべてのモダンブラウザで対応していますので、どんどん使っていきましょう！

今までの方法と今回の方法、何が違うの？ということで調べたところ、`color-scheme`は Web ページで対応するカラースキーマ(モード)を主張するものだとわかりました。ただ、Web ページ側の主張通りいかない場合もありますので、調整結果を把握しておくとよさそうです。

引き続き、NTTテクノクロス Advent Calendar 2024 (シリーズ2) をお楽しみください！