🎨

センスがなくてもいい感じにCSSで配色して、楽にダークモードにも対応したい(Radix Colors)

2024/06/08に公開

css、書いてますか?私は最近頑張ってます。

デザインセンス、ありますか?私はないです。

でも配色いい感じにしたい!ダークモードとライトモードもつけたい!
という欲を最低限のデザインセンスと労力でどうにかできそうな方法(ライブラリ)を見つけたので紹介します。

Radix Color

今回利用したのは Redix Color。
Radix UI といえば React のヘッドレス UI で有名ですが、こちらは純粋に色のみを扱うライブラリです。

https://www.radix-ui.com/colors

css のみのライブラリですので、フレームワーク関係なく利用できます。私は svelte で利用しています。

中身も超シンプル。ひとつ例を見てみましょう。

https://cdn.jsdelivr.net/npm/@radix-ui/colors@latest/gray.css

gray.css
:root, .light, .light-theme {
  --gray-1: #fcfcfc;
  --gray-2: #f9f9f9;
  --gray-3: #f0f0f0;
  --gray-4: #e8e8e8;
  --gray-5: #e0e0e0;
  --gray-6: #d9d9d9;
  --gray-7: #cecece;
  --gray-8: #bbbbbb;
  --gray-9: #8d8d8d;
  --gray-10: #838383;
  --gray-11: #646464;
  --gray-12: #202020;
}

@supports (color: color(display-p3 1 1 1)) {
  @media (color-gamut: p3) {
    :root, .light, .light-theme {
      --gray-1: color(display-p3 0.988 0.988 0.988);
      --gray-2: color(display-p3 0.975 0.975 0.975);
      --gray-3: color(display-p3 0.939 0.939 0.939);
      --gray-4: color(display-p3 0.908 0.908 0.908);
      --gray-5: color(display-p3 0.88 0.88 0.88);
      --gray-6: color(display-p3 0.849 0.849 0.849);
      --gray-7: color(display-p3 0.807 0.807 0.807);
      --gray-8: color(display-p3 0.732 0.732 0.732);
      --gray-9: color(display-p3 0.553 0.553 0.553);
      --gray-10: color(display-p3 0.512 0.512 0.512);
      --gray-11: color(display-p3 0.392 0.392 0.392);
      --gray-12: color(display-p3 0.125 0.125 0.125);
    }
  }
}

簡潔ですね。

使い方

https://www.radix-ui.com/colors/docs/overview/installation

CDN から直接持ってくるか、npm install して css に並べるだけで利用できます。

html
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@radix-ui/colors@latest/gray.css"
/>
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@radix-ui/colors@latest/blue.css"
/>
...

または

global.css
@import '@radix-ui/colors/gray.css';
@import '@radix-ui/colors/gray-dark.css';
@import '@radix-ui/colors/red.css';
@import '@radix-ui/colors/red-dark.css';
...

Radix Color では同系統の色が 12 段階用意されています。

この12色から選ぶということで、ユーティリティファーストな (tailwindっぽい) 雰囲気も感じるライブラリです。

ただし、要素の提供がクラスではなく、css 変数ですので利用方法は異なります。
css を別途定義して、この中に組み込んでいきます。

html
<button class="button">Blue Button</button>
css
button {
    background-color: var(--blue-9);
}

Radix Colors の使い方はこれだけ。とってもシンプルですね。

Radix Colors を使う利点

存在感が小さい

radix colors は色指定を補助するだけの、小さなライブラリです。

環境を問わず組み込みやすく、取り回しは抜群!

色選択の基準が明示されていて迷わない

12 段階の色それぞれが、どの場面で使われるべきなのかのガイドラインを Radix Colors 側で提供してくれています。ありがたい!!

https://www.radix-ui.com/colors/docs/palette-composition/understanding-the-scale

Step Use Case
1 App background
2 Subtle background
3 UI element background
4 Hovered UI element background
5 Active / Selected UI element background
6 Subtle borders and separators
7 UI element border and focus rings
8 Hovered UI element border
9 Solid backgrounds
10 Hovered solid backgrounds
11 Low-contrast text
12 High-contrast text

ダークモード対応

なんと、親に .dark 属性をつけるだけで全てダークモードに切り替わってくれます。

2個並べているうちの、上が普通の指定, 下が Radix Colors で指定したものです。

もちろんイメージ通りにならない箇所は手動調整が必要ですが、ダークモード対応のコストは大きく減らせそうです。

色に困ったら Radix Colors, いかがでしょう?




おまけ:ダークモードの(自動)切り替え

ダークモードを自動検出する方法と、切り替えボタンの実装例をおまけにつけておきます。

svelte での実装例になりますが、極めてシンプルな内容ですので各種環境へ容易に移植できると思います。

リポジトリ:

https://github.com/para7/sample-radix-color-svelte

【仕様】

  • ダーク・ライトの2モードのみ。「システム」の選択肢は作らない。
  • localStorage の theme キーに、最後に選択した状態を記録しておき、次回表示時はそれを利用する
  • サイトを初めて開いた際(localStorageに値がない時)は OS の設定を認識し、初期化する

UI の 描画が始まる前に OS モードを識別する

ダークモード化の際にちらつきが発生しないよう、メインのレンダリングがくる前に setup.js を読み込ませます。

app.html
<!-- ダークテーマの事前適用を行う -->
<script src="./setup.js"></script>

<body data-sveltekit-preload-data="hover">
  <div style="display: contents">%sveltekit.body%</div>
</body>

setup.js の中身はこんな感じ。

static/setup.js
let userTheme = localStorage.theme;

// light, dark以外の値だったら検出を行う
if (!['light', 'dark'].includes(userTheme)) {
  const systemIsDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

  const detectedTheme = systemIsDark ? 'dark' : 'light';

  localStorage.setItem('theme', detectedTheme);
  userTheme = detectedTheme;
}

// 初回ダークモード適用
if (userTheme === 'dark') {
  document.documentElement.classList.add('dark');
}

localStorage が検出できればそれを採用。

拾えなければ matchMedia('(prefers-color-scheme: dark)') で判定し、結果を localStorage に保存します。

ダークモードが必要であれば、 document.documentElement.classList.add('dark'); で付与すれば全体に影響が出ます。


切り替えボタン

ボタン押下時にこんな感じのスクリプトを発火させれば切り替わるはずです。
class の付け替えと localstorage の変更を行っているだけです。

const onClick = () => {
  if (document.documentElement.classList.contains('dark')) {
    document.documentElement.classList.remove('dark');
    localStorage.setItem('theme', 'light');
  } else {
    document.documentElement.classList.add('dark');
    localStorage.setItem('theme', 'dark');
  }
};

svelte5 だとこう。

theme-switch.svelte
<script lang="ts">
  const onClick = () => {
    if (document.documentElement.classList.contains('dark')) {
      document.documentElement.classList.remove('dark');
      localStorage.setItem('theme', 'light');
    } else {
      document.documentElement.classList.add('dark');
      localStorage.setItem('theme', 'dark');
    }
  };
</script>

<button class="button" onclick={onClick}> change theme! </button>

おわり。

Discussion