あんまり手間をかけない、なんちゃってダークモードUIの作り方


これは何

  • Dark ModeのUIを「厳密ではないけど手間をかけずにそれっぽく作る」ためのTipsを書いた記事です
  • あくまでなんちゃってです、ご了承ください
    • 本当は手を入れないといけない箇所が色々あります
    • 暗い背景にあわせてメインカラーの明度や彩度を調整するとか
    • Light Modeでbox-shadowを使ってelevationを表現している箇所は、box-shadowをやめてsurfaceの色の違いelevationを表現するとか

完成品

GitHub Pagesで公開しました。
ページを開いた上で、システムの設定でLightとDarkを入れ替えて見てください。
リロード無しでUIが切り替わります。

スクリーンショットも載せておきます。

Light Mode Dark Mode

おおまかな仕組み

@media (prefers-color-scheme: dark)とCSS Custom properties(CSS変数)を組み合わせます。

@media (prefers-color-scheme: dark)はメディアクエリの一種で、ユーザーがシステム設定でLight ModeをDark Modeのどちらを選んでいるかを判別できます。
そして、予めCSS変数で定義しておいた色だけを使ってスタイリングをすれば1つのコードでLightとDarkどちらのUIも組めるようになる、という仕組みです。

実際のコード

全てのコードはGitHubをご覧ください。
この記事の中ではCSSだけにフォーカスします。

CSS変数で使う色をひとしきり定義する

:root要素の中でCSS変数を定義すると、ページ内のどこでも使えるようになります。
通常用(=Light Mode)のパレットと、Dark Mode用のパレットをそれぞれ定義しています。

style.css
:root {
  --text-high-emphasis: rgba(0 0 0 / 0.87);
  --text-medium-emphasis: rgba(0 0 0 / 0.6);
  --text-on-primary: rgba(255 255 255 / 0.87);
  --primary: #0d47a1;
  --background: #f6f6f6;
  --surface: #fff;
}

@media (prefers-color-scheme: dark) {
  :root {
    --text-high-emphasis: rgba(255 255 255 / 0.87);
    --text-medium-emphasis: rgba(255 255 255 / 0.6);
    --text-on-primary: rgba(0 0 0 / 0.87);
    --primary: #ffca28;
    --background: #212121;
    --surface: #2f2f2f;
  }
}

Sassの変数ではダメ?

スタイルを組む上ではSassの変数の方が馴染み深い方も多いかもしれませんが、それではNGです。
このようにコードを書いたとして、Light Mode用の色が適用されることはありません。

:root {
  $text-high-emphasis: rgba(0 0 0 / 0.87) !global;
  $text-medium-emphasis: rgba(0 0 0 / 0.6) !global;
  $text-on-primary: rgba(255 255 255 / 0.87) !global;
  $primary: #0d47a1 !global;
  $background: #f6f6f6 !global;
  $surface: #fff !global;

  @media (prefers-color-scheme: dark) {
    $text-high-emphasis: rgba(255 255 255 / 0.87) !global;
    $text-medium-emphasis: rgba(255 255 255 / 0.6) !global;
    $text-on-primary: rgba(0 0 0 / 0.87) !global;
    $primary: #ffca28 !global;
    $background: #212121 !global;
    $surface: #2f2f2f !global
  }
}

Sassの変数はあくまで「Sass上では同じ値として繰り返し使える」存在です。
CSSに変換された後は動的に値を変更できないため、後に書いてある(prefers-color-scheme: dark)のルールしか適用されません。
(もちろん、darkを先、lightを後と順番を変えて書いたらlightのルールしか適用されません。)

定義したCSS変数でスタイルを組む

あとは、定義したCSS変数を使ってコードを書いていくだけです。
普通のCSSを書くのとほとんど変わりません。

実際のコードから一部抜粋
html {
  background-color: var(--background);
  color: var(--text-high-emphasis);
}

.main {
  background-color: var(--surface);
  border-radius: 16px;
  padding: 32px;
  width: min(640px, 100%);
}

.note {
  color: var(--text-medium-emphasis);
  font-size: 12px;
  margin-top: 16px;
}

.button {
  background-color: var(--primary);
  border-radius: 8px;
  color: var(--text-on-primary);
  font-size: 18px;
  font-weight: 700;
  margin-top: 32px;
  padding: 12px 16px;
}

var(--somecolor)の書式になっている箇所がCSS変数を用いている箇所です。
Dark Modeのためだけのコードは1行も書いていません。

カラーシステムの設計

ちなみに本筋からは外れますが、色の設計をするときはMaterial Designのcolor systemを参考にすると分かりやすいと思います。

  • 背景色(Background)
  • 要素の表面の色(Surface)
  • メインの色(Primary)

といったように考えることで、そもそも秩序ある配色でプロダクトを作れるようになります。
これはCSSだけの話ではなく、UIのモックアップ作成のときから考えておけると良い項目です。

まとめ

  • @media (prefers-color-scheme: dark)とCSS Custom properties(CSS変数)を組み合わせる
    • Sassの変数ではNG
  • 定義した色を用いてスタイリングをする
    • Material Designのcolor systemの考え方を取り入れると分かりやすい