Tailwind CSS のダークモードで System, Light, Dark を切り替える
ダークモードの設定では、OS の設定と同期させるか、ライトモードまたはダークモードに手動で切り替えるかの 3 つの選択肢が用意されていることがあります。手動でライトモードとダークモードを切り替える場合に比べて、OS の設定を自動で反映できるメリットがあります。
ダークモードを設定できるサイトやアプリケーションでは、大きくわけて 3 つの方法が考えられます。
- OS の設定に合わせてダークモードに切り替える
- ユーザーが手動でライトモードまたはダークモードに切り替える
- ユーザーが手動でライトモード、ダークモード、OS の設定に合わせるかを切り替える
OS の設定に合わせてダークモードに切り替える場合には、prefers-color-scheme
という CSS メディアクエリを使用します。このメディアクエリは、ユーザーの OS の設定がダークモードになっている場合にのみスタイルが適用されます。ユーザーが明示的に設定を切り替えられない不便さはありますが、実装が簡単です。
ユーザーが手動でライトモードまたはダークモードに切り替える場合には、大抵の場合トグルボタンやスイッチの ON/OFF で切り替えられるようになっています。
この場合には、初期設定では OS の設定に合わせてダークモードに切り替えるようにしておき、ユーザーが手動で切り替えた場合には LocalStorage などに保存しておき、次回からはその設定を読み込むようにします。
ダークモードの設定では、OS の設定と同期させるか、ライトモードまたはダークモードに手動で切り替えるかの 3 つの選択肢が用意されていることがあります。例えば、Tailwind CSS の公式サイトでは、System
と Light
と Dark
の 3 つを切り替えることができます。
手動でライトモードとダークモードを切り替える場合に比べて、System
はどのようなメリットが有るのでしょうか?それは OS の設定を変更した時に自動で反映してくれることにあるでしょう。
例えば、Mac OS のダークモードの設定では時間帯によって自動で切り替わるように設定できます。このような場合には、ユーザーが手動でライトモードとダークモードを切り替えるよりも、OS の設定に合わせて自動で切り替わる方が便利です。
それでは、Tailwind CSS のダークモードで System
と Light
と Dark
の 3 値を切り替える方法を見ていきましょう。
Tailwind CSS の設定
Tailwind CSS でタークモードはデフォルトでは OS の設定に合わせて切り替わるようになっています。今回は手動でライトモードとダークモードを切り替えるように設定を変更していきます。その場合には class
ストラテジーを使用することになります。class
ストラテジーでは HTML の祖先に dark
クラスが付与されている場合にのみダークモードのスタイルが適用されます。
class
ストラテジーを使用する場合には tailwind.config.js
の darkMode
に class
を指定します。
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: 'class',
// ...
}
ダークモードの初期設定
class
ストラテジーを使用する場合には、ページが読み込まれた際にダークモードで表示するかどうかを決定して、dark
クラスを付与する必要があります。dark
クラスは HTML のルート要素である <html>
に付与することになるでしょう。
ダークモードで表示するかの決定は、以下のプロセスによって行われます。
- LocalStorage に
theme
が保存されていないまたは、theme
がsystem
の場合 OS の設定に合わせてダークモードで表示するかどうかを決定する - LocalStorage に
theme
が保存されていて、theme
がdark
の場合はダークモードで表示す - それ以外の場合にはライトモードで表示する
// LocalStorage に theme が保存されていない or theme が system の場合
if (!('theme' in localStorage) || localStorage.theme === 'system') {
// OS の設定を読み取る
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
// OS の設定がダークモードの場合、<html> に dark クラスを付与する
document.documentElement.classList.add('dark')
}
// LocalStorage に設定を保存する
localStorage.setItem('theme', 'system')
} else if (localStorage.theme === 'dark') { // LocalStorage に theme が保存されていて、theme が dark の場合
document.documentElement.classList.add('dark')
} else { // それ以外の場合
document.documentElement.classList.remove('dark')
}
このスクリプトは CSS が読み込まれる前に実行される必要があります。さもなければ、ダークモードで表示する前に一瞬ライトモードで表示されてちらつく感じがします。一般的な React のアプリケーションでは index.html
の <head>
タグ内の <style>
タグの前に <script>
タグを配置することで、CSS が読み込まれる前に実行されるようになります。
OS のダークモードの設定を読み取るには window.matchMedia を使用します。window.matchMedia
はメディアクエリーを引数に受け取り、MediaQueryList オブジェクトを返します。
MediaQueryList
オブジェクトは matches
プロパティを持っており、メディアクエリに合致するかどうかを表す boolean
値を返します。また、change
イベントを監視することにより、メディアクエリの状態が変化した際にコールバック関数を実行できます。
window.matchMedia("(prefers-color-scheme: dark)").matches
が true
を返す場合には OS の設定がダークモードであることを意味します。
OS の設定が変更された際に自動で反映する
ダークモードの設定として System
が選択されている場合には、OS の設定が変更された際に自動でダークモードの設定が反映される必要があるでしょう。これは前述した MediaQueryList
オブジェクトの change
イベントを監視することにより実現できます。
window.matchMedia('(prefers-color-scheme: dark)')
が返す値に add.addEventListener('change', callback)
を呼び出すことで、OS の設定が変更された際に callback
関数が実行されるようになります。
// OS の設定が変更された際に実行されるコールバック関数
const mediaQueryLlistener = (e: MediaQueryListEvent) => {
if (localStorage.theme === 'system') {
if (e.matches) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
}
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', mediaQueryLlistener)
コールバック関数内では LocalStorage の値が System
の場合のみ処理を実行します。明示的にライトモード、ダークモードを選択している場合には、OS の設定が変更されても反映されないようにするためです。
コールバック関数は引数で MediaQueryListEvent を受け取ります。MediaQueryListEvent
オブジェクトは matches
プロパティを持っており、メディアクエリに合致するかどうかを表す boolean
値を返します。
この値が true
の場合には OS の設定がダークモードに変更されたことを意味しますから、<html>
に dark
クラスを付与します。false
の場合には反対に <html>
から dark
クラスを削除します。
これで、実際に OS の設定を変更するたびにダークモードの設定が自動で反映されることが確認できます。
ダークモードの切り替え
ダークモードの切り替えは LocalStorage の theme
の値をの変更と <html>
タグの dark
クラスのトグルで実現できます。System
、Light
、Dark
の 3 値を選択して変更しますから、セレクトボックスで切り替えることが考えられます。
React で実装すると、以下のような例になるでしょう。
type Theme = 'system' | 'light' | 'dark'
const ThemeSwitcher: React.FC = () => {
// LocalStorage に保存されている値を初期値として設定する
const [theme, setTheme] = useState<Theme>(localStorage.theme || 'system')
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value as Theme
setTheme(value)
localStorage.setItem('theme', value)
if (value === 'dark') {
document.documentElement.classList.add('dark')
} else if (value === 'light') {
document.documentElement.classList.remove('dark')
} else {
// System が選択された場合は OS の設定を見て切り替える
document.documentElement.classList.toggle('dark', window.matchMedia('(prefers-color-scheme: dark)').matches)
}
}
return (
<select value={theme} onChange={handleChange}>
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
)
}