【CSS/JS】WebサイトをOSのダークモードに自動対応させ、手動切り替え機能も実装する方法

近年、OSやアプリケーションで広く採用されている「ダークモード」。目の負担軽減や、有機ELディスプレイでのバッテリー消費抑制といったメリットから、多くのユーザーに好まれています。
現代のWebサイトにおいて、ユーザーのOS設定を尊重し、ダークモードに自動対応することは、快適なユーザー体験を提供する上で非常に重要な要素となっています。

この記事では、基本的なCSSでの対応方法から、JavaScriptを使った高度な制御、さらにはユーザーが任意にテーマを切り替え、その設定を記憶させる方法まで、段階的かつ網羅的に解説します。

Level 1: CSSメディアクエリだけで実現する最もシンプルな方法

まず、最も手軽にOSのダークモード設定をWebサイトに反映させる方法です。
CSSのメディアクエリ prefers-color-scheme を使えば、JavaScriptを一切使わずに対応できます。

解説

prefers-color-scheme は、ユーザーがシステム(OS)で設定している外観モード(ライト/ダーク)を判別するためのメディア特性です。
これを利用して、ダークモードの場合に適用したいスタイルを記述します。

実装コード (CSS)

/* --- デフォルトのスタイル(ライトモード用) --- */body { color: #333; background-color: #fff; transition: color 0.3s, background-color 0.3s; /* スムーズな切り替え効果 */}/* --- OSがダークモードの場合に適用 --- */@media (prefers-color-scheme: dark) { body { color: #eee; background-color: #121212; }}
CSS

ポイント:

  • まず、ライトモード(デフォルト)のスタイルを定義します。
  • @media (prefers-color-scheme: dark) のブロック内に、ダークモード用のスタイルを上書きする形で記述します。
  • transition を設定しておくと、OSの設定を切り替えた際に、色が滑らかに変化し、ユーザー体験が向上します。

この方法のメリット:

  • CSSだけで完結するため、非常にシンプルで軽量。
  • JavaScriptが無効な環境でも動作する。

この方法のデメリット:

  • あくまでOSの設定に追従するだけなので、ユーザーがサイト上で個別に「ライトモードで見たい」といった選択ができない。

Level 2: JavaScriptで外観モードを判定・制御する

次に、JavaScriptを使ってより能動的にテーマを制御する方法です。これにより、手動切り替えボタンのような、より高度な機能実装への道が開かれます。

解説

window.matchMedia() メソッドを使うことで、CSSのメディアクエリが現在の状態に一致するかどうかをJavaScriptで判定できます。'(prefers-color-scheme: dark)' を引数に渡すことで、ダークモードが有効かどうかを true / false の真偽値で取得できます。

さらに、.addListener() を使えば、ユーザーがOSの設定を変更した瞬間を検知して、リアルタイムで処理を実行することも可能です。

実装コード (JavaScript)

このコードは、OSの設定に応じて<body>タグに is-theme-light または is-theme-dark というクラスを付与します。CSSは、これらのクラスセレクタに対してスタイルを定義します。

// OSのダークモード設定を監視するconst darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');// 現在のモードに応じてクラスをbodyに付与する関数const applyTheme = (isDarkMode) => { if (isDarkMode) { document.body.classList.remove('is-theme-light'); document.body.classList.add('is-theme-dark'); } else { document.body.classList.remove('is-theme-dark'); document.body.classList.add('is-theme-light'); }};// OSの設定が変更された時に発火darkModeMediaQuery.addListener((e) => { applyTheme(e.matches);});// 初期読み込み時に一度だけ実行applyTheme(darkModeMediaQuery.matches);
JavaScript

対応するCSS

/* --- ライトモード用のスタイル --- */body.is-theme-light { color: #333; background-color: #fff;}/* --- ダークモード用のスタイル --- */body.is-theme-dark { color: #eee; background-color: #121212;}/* スムーズな切り替え効果 */body { transition: color 0.3s, background-color 0.3s;}
CSS

この方法のメリット:

  • クラスを付け替える方式なので、CSSの見通しが良くなる。
  • JavaScriptでテーマの状態を管理できるため、他の処理との連携がしやすくなる。

Level 3: 手動切り替えと設定の永続化(localStorage)

最も実践的でユーザーフレンドリーな実装です。以下の要件を満たします。

  1. 初回アクセス時: OSの設定を自動で反映する。
  2. 手動切り替え: ユーザーがボタンで自由にテーマを切り替えられる。
  3. 設定の記憶: ユーザーが選択したテーマをブラウザのlocalStorageに保存し、次回以降の訪問時もその設定を維持する。

実装コード (HTML)

テーマを切り替えるためのボタンを配置します。

<button class="js-theme-switch" aria-label="テーマを切り替える"> <span class="js-theme-icon"></span></button>
HTML

実装コード (JavaScript) - 完成版

これまでのロジックを統合し、localStorageへの保存・読み込み機能を追加します。

document.addEventListener('DOMContentLoaded', () => { const themeSwitch = document.querySelector('.js-theme-switch'); if (!themeSwitch) return; const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); let currentTheme = localStorage.getItem('theme-setting'); // テーマを適用し、localStorageを更新する関数 const applyTheme = (theme) => { // bodyのクラスを更新 if (theme === 'dark') { document.body.classList.add('is-theme-dark'); document.body.classList.remove('is-theme-light'); } else { document.body.classList.add('is-theme-light'); document.body.classList.remove('is-theme-dark'); } // localStorageに保存 localStorage.setItem('theme-setting', theme); currentTheme = theme; }; // 切り替えボタンのクリックイベント themeSwitch.addEventListener('click', () => { const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; applyTheme(newTheme); }); // OSの設定変更を監視 darkModeMediaQuery.addListener((e) => { // ユーザーが手動で設定していない場合のみ、OS設定に追従 if (!localStorage.getItem('theme-setting')) { applyTheme(e.matches ? 'dark' : 'light'); } }); // --- 初期読み込み時のテーマ決定ロジック --- if (currentTheme) { // 1. localStorageに設定があればそれを優先 applyTheme(currentTheme); } else { // 2. localStorageに設定がなければ、OSの設定に従う applyTheme(darkModeMediaQuery.matches ? 'dark' : 'light'); }});
JavaScript

コードのポイント解説:

  1. 初期読み込み時の優先順位:
    • まずlocalStorage'theme-setting'というキーで保存された値があるか確認します。
    • 値があれば、その設定('dark' or 'light')を最優先で適用します。
    • 値がなければ(初回訪問時など)、OSの設定 (darkModeMediaQuery.matches) を見てテーマを決定します。
  2. クリックイベント:
    • ボタンがクリックされるたびに、現在のテーマとは逆のテーマをapplyTheme関数に渡して適用します。
    • applyTheme関数内でlocalStorageが更新されるため、ユーザーの選択が保存されます。
  3. OS設定変更の監視:
    • addListenerのコールバック内で、localStorageに手動設定が存在しない場合のみOS設定に追従するように変更しました。これにより、ユーザーが一度手動でテーマを切り替えた後は、OSの設定を変更してもサイトのテーマは上書きされず、ユーザーの選択が尊重されます。

まとめ

ダークモードへの対応は、もはや特別な機能ではなく、Webサイトの「おもてなし」の一つです。

  • Level 1 (CSSのみ): 手軽に実装したい場合に最適。
  • Level 2 (JSでの検知): より複雑な制御の第一歩。
  • Level 3 (手動切り替え & 保存): 最高のユーザー体験を提供するための完成形。

今回紹介したコードをベースに、あなたのWebサイトにもダークモード対応を導入し、より多くのユーザーにとって快適な閲覧環境を提供してみてはいかがでしょうか。

read next