title: color-schemeとprefers-color-schemeのつかいかた
author: Amane Katagiri
summary: カラーテーマを自動で切り替える
date: 2021-03-09T19:45:00+09:00
category: tech
license: CC BY 4.0
rating: sfw
ai_summary: ウェブサイトのライト・ダークモード対応について、`color-scheme`や`prefers-color-scheme`を用いた実装方法を解説する記事です。サイト「あまねけ！」を例に、OS設定との自動連動と手動切り替えを両立させる仕組みやブラウザの互換性、CSSのみでトグル機能を実装するテクニックを紹介。一部の古いブラウザへの配慮や、実装における注意点についても詳しくまとめられています。
ai_keywords: color-scheme, prefers-color-scheme, ダークモード, ライトモード, CSS, メディアクエリ, ブラウザ互換性, あまねけ！, フロントエンド開発, UI切り替え

[TOC]

# 概要

* metaタグまたはスタイルシートで `color-scheme` を設定することで、ブラウザにカラーテーマを指定することができる。
* メディア特性の `prefers-color-scheme` を用いることで、ユーザがどのようなカラーテーマを要求しているかを取得できる。
* あまねけ！では `prefers-color-scheme` を用いてカラーテーマを自動で切り替え、従前より提供していた手動での切り替え機能も引き続き使用できるようにした。

# `color-scheme` によるカラーテーマの指定

## スタイルシートでの指定

[CSSプロパティの `color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme) を設定すると、ブラウザにカラーテーマを指定することができる。`color-scheme` には以下の属性を設定できる。

* `normal` [^default-light]
* `light`
* `dark`

[^default-light]: 多くのブラウザでは明るいカラーテーマとなる。

`light` および `dark` はスペース区切りで2つ以上並べることができ、この場合は先に指定したカラーテーマを推奨していることを示せる。

```css
:root {
  color-scheme: dark light;
}
```

カラーテーマを指定することで、自前でスタイルシートを用意することなくスクロールバーやフォーム要素を含むページの色合いを切り替えることができる。ただし、 `color-scheme` はFirefox[^firefoxver]およびInternet Explorerには対応していない。そのため、Firefoxが対応するまでは暗いカラーテーマのスタイルシートを用意しておく必要があるかもしれない。

[^firefoxver]: 現時点での最新バージョンはFirefox 86.0である。

`color-scheme` は[全ての要素に適用可能](https://www.w3.org/TR/css-color-adjust-1/#color-scheme-prop)とされている。しかし、body要素以下に `color-scheme: dark` を適用しつつ、ルート要素（ `:root` または `html` ）には `color-scheme: dark` を指定しなかった場合は、文字色や背景色が変わらないなどの致命的な不具合がみられた[^chromever]。

[^chromever]: Google Chrome 88.0で確認した。

## metaタグでの指定

`color-scheme` は[metaタグ](https://developer.mozilla.org/ja/docs/Web/HTML/Element/meta)でも設定でき、ルート要素に対して `color-scheme` プロパティを設定するのと同じ効果を持つ。

```html
<meta name="color-scheme" content="dark light">
```

# `prefers-color-scheme` による優先カラーテーマの取得

メディア特性の [`prefers-color-scheme`](https://developer.mozilla.org/ja/docs/Web/CSS/@media/prefers-color-scheme) を用いることで、ユーザーがどのようなカラーテーマを優先的に要求しているかを取得できる。 `prefers-color-scheme` は以下の値を持ちうる。

* `light`
* `dark`

`prefers-color-scheme` をメディアクエリで用いることにより、ユーザの要求に合わせてスタイルを切り替えることができる。例えば、以下のようなスタイルシートはユーザの優先カラーテーマに合わせて背景色を切り替える。

```css
/* 暗いカラーテーマ */
@media screen and (prefers-color-scheme: dark) {
  body {
    background-color: black;
  }
}

/* 明るいカラーテーマ */
@media screen and (prefers-color-scheme: light) {
  body {
    background-color: white;
  }
}
```

`prefers-color-scheme` は多くのブラウザで対応しているものの、残念ながらInternet Explorerには対応していない[^firefoxmdn]。上記のように全てのカラーテーマを `prefers-color-scheme` メディアクエリに閉じ込めてしまうと、未対応のブラウザではいずれのスタイルも適用できないため、必要に応じてどちらかのテーマにフォールバックさせてもよいかもしれない。

```css
/* 明るいカラーテーマ（またはデフォルト） */
@media screen {
  body {
    background-color: white;
  }
}

/* 暗いカラーテーマ */
@media screen and (prefers-color-scheme: dark) {
  body {
    background-color: black;
  }
}
```

[^firefoxmdn]: [MDNの `prefers-color-scheme`](https://developer.mozilla.org/ja/docs/Web/CSS/@media/prefers-color-scheme)ではFirefox Androidも未対応になっているが、手元のFirefox Daylight 86.1.1では動作している。

# あまねけ！での対応

あまねけ！では、これまでページ上部に toggle color scheme ボタン（切り替えボタン）を設置して、ブラウザのデフォルトカラーテーマ[^default-light]とオリジナルの暗いカラーテーマを切り替えられるようにしていた。

## `color-scheme` の採用

まず、オリジナルの暗いカラーテーマを廃止し、 `color-scheme` を用いてカラーテーマを切り替えることを検討したが、以下の理由で不採用とした。そのため、スクロールバーの色は従来通り明るいままとなっている[^scrollbar-color]。

[^scrollbar-color]: ただし、 `color-scheme` の指定とは関係なく、スクロールバーの色がbody要素の背景色に追従するブラウザ（Firefoxなど）もある。

* metaタグでの `color-scheme` の指定は、読み込み後に手動で切り替えることができない。
* CSSプロパティでの `color-scheme` の指定は、[疑似クラス `:has()`](https://developer.mozilla.org/ja/docs/Web/CSS/:has) がなければ手動で切り替えることができない。
* 一部のブラウザでは `color-scheme` でのカラーテーマの切り替えに対応していない。

ただし、ここでいう「手動で切り替える」とはスタイルシートのみを用いて切り替えることを指しており、JavaScriptでのプロパティの書き換えなどは含まない。

## `prefers-color-scheme` の採用

次に、 `prefers-color-scheme` を用いて初期表示時のカラーテーマを自動で切り替えることを検討した。

`prefers-color-scheme` に合わせて初期表示時のカラーテーマを設定した場合、切り替えボタンを用いた以下のような切り替えパターンが考えられる。

* 明るいカラーテーマで表示する場合
    * (A) `prefers-color-scheme` に未対応で切り替えボタンを偶数回クリックした
    * (B) `prefers-color-scheme: light` で切り替えボタンを偶数回クリックした
    * (C) `prefers-color-scheme: dark` で切り替えボタンを奇数回クリックした
* 暗いカラーテーマで表示する場合
    * (D) `prefers-color-scheme` に未対応で切り替えボタンを奇数回クリックした
    * (E) `prefers-color-scheme: light` で切り替えボタンを奇数回クリックした
    * (F) `prefers-color-scheme: dark` で切り替えボタンを偶数回クリックした

これまでの実装と影響範囲を考慮し、最終的には以下のような実装を行った。

```css
@media screen and (prefers-color-scheme: light) {
  /* (E) 奇数回クリックした際の挙動（暗いカラーテーマ） */
}
@media screen and (prefers-color-scheme: dark) {
  /* (F) 偶数回クリックした際の挙動（暗いカラーテーマ） */
}
```

### `prefers-color-scheme` に未対応のブラウザへの対処

切り替えのロジック上、全てのパターンを網羅するには `prefers-color-scheme` に未対応のブラウザと `prefers-color-scheme: dark` であるブラウザを区別する必要がある。しかし、 `prefers-color-scheme` に未対応のブラウザでは `prefers-color-scheme` を含むメディアクエリを一切使用できず、 `prefers-color-scheme` に未対応かどうかを明示的には検出できない。

この問題は、明るいカラーテーマをブラウザに頼らず明示的に設定することで解決できる。具体的には、フォールバックのスタイルには偶数回で明るく、奇数回で暗く切り替えるように設定し、 `prefers-color-scheme: dark` のスタイルでそれらを逆に上書きすればよい。

```css
@media screen {
  /* (A,B) 偶数回クリックした際の挙動（明るいカラーテーマ） */
  /* (D,E) 奇数回クリックした際の挙動（暗いカラーテーマ） */
}
@media screen and (prefers-color-scheme: dark) {
  /* (C) 奇数回クリックした際の挙動（明るいカラーテーマ） */
  /* (F) 偶数回クリックした際の挙動（暗いカラーテーマ） */
}
```

しかし、明るいカラーテーマを指定する手間と古いブラウザに対応するメリットを比較して、今回は `prefers-color-scheme` に未対応のブラウザには対処しないことにした。今後、これらの環境では切り替えボタン自体が表示されない。もし切り替えボタンが使えなくなって困っていたら、すぐに対処するのでコメントなどで知らせてほしい。

### 強制ダークモードへの対処

また、サイトの `color-scheme` 指定にかかわらずブラウザのデフォルトカラーテーマを暗いものに切り替える機能、いわゆる強制ダークモードを適用している場合は、切り替えボタンをクリックしても2つの暗いカラーテーマ（ブラウザのデフォルトとサイトのオリジナル）しか使用できない。これについても、オリジナルの明るいカラーテーマを指定すれば解決できる。とはいえ、強制ダークモードを適用したいユーザが明るいカラーテーマを希望することは少ないと考えられるため、こちらも特に対処しない。

# おまけ: チェックボックスでスタイルを切り替える

チェックボックスの状態を区別する [`:checked 擬似クラスセレクタ`](https://developer.mozilla.org/ja/docs/Web/CSS/:checked)と[一般兄弟結合子 `~`](https://developer.mozilla.org/ja/docs/Web/CSS/General_sibling_combinator)を用いることで、CSSの機能だけでチェックボックスの状態に応じてスタイルを切り替えられる。現在、あまねけ！ではこの手法でカラーテーマを切り替えている。

この手法の欠点は、チェックボックスより階層が上の要素にはスタイルを当てられないということである。現状主要なブラウザで使用できる範囲では、JavaScriptを用いて親を辿らなければならない。「ある状態のチェックボックスを子に持つようなbody要素」のようなセレクタには、[疑似クラス `:has()`](https://developer.mozilla.org/ja/docs/Web/CSS/:has) の実装が必要である。

```html
<html>
  <head>
    <title>チェックボックスでスタイルを切り替える</title>
    <style>
    #main {
        background-color: red;
    }
    input[type="checkbox"]#toggle:checked ~ #main {
        color: #eee;
        background-color: blue;
    }
    </style>
  </head>
  <body>
    <input id="toggle" type="checkbox">
    <section id="main">
      <div>チェックボックスでスタイルを切り替える</div>
    </section>
  </body>
</html>
```
[ブラウザで表示](https://gist.githack.com/amane-katagiri/d52284d464386bdfdac8be109c5d4534/raw/72e4908b38619fffbd62200eaeccc77e7f2db810/)

# 参考

* [meta要素のname=color-schemeについて — Hail2u](https://hail2u.net/blog/meta-name-color-scheme.html)
* [color-scheme - CSS: Cascading Style Sheets | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme)
* [prefers-color-scheme - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/@media/prefers-color-scheme)
