Tailwind CSS でダークモードを実装する(React での切り替えボタンの実装コード例あり)
Tailwind CSS でダークモードを実装する
Tailwind CSS でダークモードを実装する方法を紹介します。Tailwind CSS は v2 からダークモード用の CSS を簡単に適用できるようになりました。
この記事では、以前私が個人開発で作成した note PDFy というサイトを例に、ダークモードの作成方法と Tailwind CSS でのダークモードの実装方法を解説します。
このサイトは note の URL を入力するだけで記事を PDF 化できるツールです。これを使うと note の記事をオフラインで読むことができます。
なお、本記事は私の所属する BASE 社の Frontend Weekly LT で発表した内容を記事化したものです。
CSS でダークモードを実装する方法
まずはダークモードに対応する CSS を設定する方法を解説します。本章の内容は、ダークモード入門 という記事を参考にしています。
ダークモードに対応する方法は 「メディアクエリ」 と 「クラス指定」 以下の2通りです。
それぞれの方法を紹介します。なお、以下では背景色が白基調の設定をダークモードと対比して「ライトモード」と呼ぶことにします。
メディアクエリ
メディアクエリで対応する場合、prefers-color-scheme
を使います。
:root {
--cText: #656765;
}
/* ダークモード時のスタイル */
@media (prefers-color-scheme: dark) {
:root {
--cText: #fcfaf2;
}
}
.text {
color: var(--cText);
}
これでテキストカラーはライトモードの時に#656765
、ダークモードの時に#fcfaf2
になります。
メディアクエリで実装する場合は、OS のダークモードの設定を参照するため、ライト/ダークモードを切り替える際には OS の設定を変更する必要があります。
なお、:root
はhtml タグを指定する擬似クラスです。
MDN曰く「:root はグローバルの CSS 変数を宣言するのに便利」とのことです。
クラスの指定
クラスを指定する場合は CSS の書き方が少し変わります。
:root {
--cText: #656765;
}
// dark というクラスがある時の色を指定
:root.dark {
--cText: #64363c;
}
.text {
color: var(--cText);
}
これで、html タグにdark
というクラス名が付与されていたら、ダークモード用の色が割り当てられるます。
// ライトモード
<html>
...
</html>
// ダークモード
<html class="dark">
...
</html>
メディアクエリの場合は OS の設定を変えない限りはライト/ダークモードの切り替えはできませんでした。
しかし、クラスの付与/削除は JS で制御できます。このため、サイト上から任意のモードを設定できるようにするためには、クラスを指定する方式を使います。
なお、ライト/ダークモードの設定の保存には SessionStorage や LocalStorage が使われるのが一般的です(今回のツールで設定を LocalStorage に保存してます)。
以下の画像では LocalStorage に key が theme
、 value が dark
の文字列が保存されていることがわかります。
JS で LocalStorage の値を取得しているため、画面をリロードしてもダークモードが維持されます。
簡易まとめ
- メディアクエリは OS の設定を参照している
- クラスを使う場合は、サイト上から自由にライト/ダークモードを設定できる
- ただし、ブラウザの SessionStorage / LocalStorage などに設定を保存する必要がある
Tailwind CSS でダークモードを実装する方法
ここでは Tailwind CSS の説明は省きます。もし Tailwind CSS を初めて知る方は 拙ブログの紹介記事 をぜひご覧ください。
ダークモード設定方法
Tailwind CSS は v2 からdark:
というクラス名の接頭辞が追加されました。この接頭辞を有効にするためには、tailwind.config.js
に以下の記述を追加します。
// tailwind.config.js
module.exports = {
darkMode: 'media', // メディアクエリ方式
// ...
}
もしくは
// tailwind.config.js
module.exports = {
darkMode: 'class', // クラス方式
// ...
}
これでダークモード用のdark:
接頭辞が利用できます。
ダークモード対応のクラス名の指定
Tailwind CSS は Utility First の CSS フレームワークです。例えば以下のようなクラス名を指定してみます。
<p class="text-black">text</p>
Tailwind CSS にこの HTML ファイルを入力すると次の CSS が出力されます(実際の出力結果とプロパティが異なりますが、ここでは説明のためにcolor
としています)。
.text-black {
color: #000000;
}
これで テキストが黒い色になります。
次に、ダークモードの時にテキストを白くしたければクラスdark:text-white
を追加します。
<p class="text-black dark:text-white">text</p>
これでビルドすると以下のような CSS が出力されます。
.text-black {
color: #000000;
}
.dark .dark\:text-white {
color: #FFFFFF;
}
これが Tailwind CSS でのダークモード対応方法です。
あとは以下の対応をすれば、サイトをダークモード化できます。
-
Headless UI の Switch などを活用し、
html
にdark
クラスを付与したり除外するボタンを作る - ボタンなどのパーツや、Primary / Secondary カラーなどに応じて適切なダークモード用の配色を選択し、クラス名に設定する
React + Tailwind CSS でダークモードの切り替えをするボタンを実装する
Tailwind CSS の公式ドキュメント Dark Mode - Tailwind CSS を参照しつつ、React でダークモード対応のボタンを作成するコード例を紹介します。
ダークモードに切り替える Custom Hooks を作成する
まずはダークモードとライトモードを切り替える Hooks を作成します。
前述のように html タグにクラス名「dark」 を付与、あるいは削除することで切り替えに対応できました。それを useEffect 内のコードで表現していきます。
// useSimpleDarkMode.ts
import { useCallback, useEffect, useState } from 'react'
type UseSimpleDarkMode = (isDark?: boolean) => {
isDarkMode: boolean
toggle: (isDark?: boolean) => void
}
// dark mode と light mode を切り替える
export const useSimpleDarkMode: UseSimpleDarkMode = (isInitialDark = false) => {
const [isDarkMode, toggleTheme] = useState<boolean>(isInitialDark)
const toggle = useCallback((isDark?) => {
if (typeof isDark === 'undefined') {
toggleTheme((state) => !state)
return
}
toggleTheme(isDark)
}, [])
useEffect(() => {
if (isDarkMode) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
}, [isDarkMode])
return { isDarkMode, toggle }
}
設定の永続化は別の Custom Hooks で対応します。useSimpleDarkMode
にはクラス名の切り替えという責務のみ担当させるため、useDarkMode
という命名はしていません。
useSimpleDarkMode をボタンに適用する
useSimpleDarkMode
をボタンに適用します。これでダークモードの切り替えが可能です。
import { useSimpleDarkMode } from './useSimpleDarkMode'
const DarkModeButton: React.VFC = () => {
const { isDarkMode, toggle } = useSimpleDarkMode()
return (
<Button onClick={() => toggle()}>
{isDarkMode ? 'light' : 'dark'}
</Button>
)
}
export default DarkModeButton
Tailwind CSS の設定をpreview.js
で読み込みます。
// src/assets/style.css
@tailwind base;
@tailwind components;
@tailwind utilities;
// .stories/preview.js
import '../src/assets/style.css'
Storybook 上で表示します。
ライトモード | ダークモード |
---|---|
なお、上記画像に適用しているボタンのスタイルはコード内では省略しています。
設定を LocalStorage に保存し、再アクセス時に LocalStorage から読み込むための Custom Hooks を作成する
上記のままではダークモードの設定を永続化できないため、useSimpleDarkMode
を利用する別の Hooks useDarkMode
を作成します。
なお、LocalStorage へのアクセスには react-use の useLocalStorage を利用しています。
import { useSimpleDarkMode } from './useSimpleDarkMode'
import { useEffect } from 'react'
import { useLocalStorage } from 'react-use'
const Theme = {
Dark: 'dark',
Light: 'light',
} as const
type UseDarkMode = () => {
isDarkMode: boolean
toggle: (isDark: boolean) => void
}
export const useDarkMode: UseDarkMode = () => {
const [value, setValue] = useLocalStorage<typeof Theme['Dark' | 'Light']>('theme')
const { isDarkMode, toggle } = useSimpleDarkMode()
const persistToggle = (isDark: boolean) => {
toggle(isDark)
setValue(isDark ? Theme.Dark : Theme.Light)
}
useEffect(() => {
if (
value === Theme.Dark ||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
toggle(true)
setValue(Theme.Dark)
} else {
toggle(false)
setValue(Theme.Light)
}
}, [value, setValue, toggle])
return { isDarkMode, toggle: persistToggle }
}
persistToggle
をボタンのonClick
に渡すことでダークモードの切り替えをしながら LocalStorage に設定を保存します。
useEffect
ではコンポーネントの初回レンダリング時にローカルストレージ、または OS の設定を読み込むようにしています。
実際にボタンに適用する際は、上記のコード例からuseSimpleDarkMode
をuseDarkMode
に差し替えるだけです。
import { useDarkMode } from './useDarkMode'
const DarkModeButton: React.VFC = () => {
const { isDarkMode, toggle } = useDarkMode()
return (
<Button onClick={() => toggle()}>
{isDarkMode ? 'light' : 'dark'}
</Button>
)
}
export default DarkModeButton
これでサイト上でダークモード、ライトモードを切り替えるボタンを実装できました!
まとめ
Tailwind CSS でダークモードに対応する方法はとても簡単です。一方、ダークモード対応の配色を選ぶのは少々骨が折れます。
ただ、それも一度決めてしまえば使いまわせるので大変なのは一番最初くらいです。
私は addon-a11y を導入した Storybook 上で、アドオンの基準にパスするようなダークモード用の配色を決めていきました。
結果、Lighthouse で高得点を得ることができました。
もともと1ページだけでパーツ数も多くないからなのですが、テストと同じでいい点数を取ると嬉しいです!
この記事を読んでくださった方のダークモード実装に対する心理的なハードルを下げられたのであれば幸いです。
Happy hacking 🎉
Discussion