ReactでCSS Modulesを用いたテーマスイッチング完全ガイド

Reactアプリケーションにおいて、ユーザー体験を向上させるためにテーマスイッチングを導入することが一般的になっています。ダークテーマやライトテーマといった見た目の変更は、視覚的な快適さを提供し、アクセシビリティを高める効果があります。これを効率的に実現する方法として、CSS Modulesを用いることでスタイルの管理を簡素化しつつ、テーマの切り替えをシームレスに行うことが可能です。本記事では、CSS Modulesを活用してReactアプリでテーマスイッチングを実現する手順を詳しく解説します。

目次

CSS Modulesとは


CSS Modulesは、CSSのスコープをモジュール単位で管理する技術で、クラス名の競合を防ぎながらスタイルを適用できます。従来のグローバルなCSSとは異なり、CSS Modulesでは各スタイルが独自の名前空間を持つため、同じクラス名を複数のコンポーネントで使用しても問題が生じません。

CSS Modulesの基本的な仕組み


CSS Modulesは、スタイルファイル内のクラス名をハッシュ化してユニークな名前に変換します。この変換は自動的に行われ、Reactコンポーネント内でスタイルをインポートして利用する際に適用されます。以下はその基本的な例です:

styles.module.css:

.button {
  background-color: blue;
  color: white;
}

Button.js:

import styles from './styles.module.css';

function Button() {
  return <button className={styles.button}>Click me</button>;
}

このコードは、.buttonクラスがハッシュ化された名前で適用され、他の.buttonクラスと競合することがありません。

ReactでのCSS Modulesのメリット

  1. クラス名の競合を防止:ユニークなクラス名を自動生成するため、スケーラブルなアプリケーション開発が容易になります。
  2. メンテナンス性向上:コンポーネントごとにスタイルを管理できるため、コードが整理され、保守が簡単になります。
  3. 動的スタイリングが容易:JavaScriptからクラス名を操作しやすく、動的なテーマスイッチングにも対応可能です。

CSS Modulesは、Reactで効率的にスタイルを管理しながらテーマスイッチングを実現するための強力なツールです。次のセクションでは、この仕組みをテーマスイッチングにどのように応用するかを解説します。

テーマスイッチングの仕組み


テーマスイッチングとは、ユーザーがアプリケーションの見た目を切り替える機能です。主にダークテーマとライトテーマを切り替えることが一般的で、ユーザー体験を向上させる重要な要素です。この機能を実現するには、スタイルの適用方法を動的に変更できる仕組みが必要です。

仕組みの基本概念


テーマスイッチングは、以下のプロセスに基づいて動作します:

  1. 状態管理:アプリケーションで現在のテーマ(ダークまたはライト)を管理する状態を保持します。これにはReactのuseStateuseContextを使用します。
  2. スタイルの分離:それぞれのテーマに対応するスタイルを用意します。CSS Modulesを利用すれば、テーマごとにスタイルをモジュール化できます。
  3. 動的スタイル適用:状態の変更に応じて適用するスタイルを切り替えます。Reactのプロパティを使って、対応するクラス名を動的に設定します。

ReactとCSS Modulesを使ったテーマスイッチング


Reactでは、テーマスイッチングの状態を管理し、CSS Modulesを使用してスタイルを動的に変更します。以下は基本的な流れです:

  1. テーマの状態管理
    useStateを使ってテーマの状態を管理します。
   const [theme, setTheme] = useState('light');
   const toggleTheme = () => {
     setTheme(theme === 'light' ? 'dark' : 'light');
   };
  1. CSS Modulesによるスタイル作成
    ダークテーマとライトテーマそれぞれのスタイルを作成します。
   /* themes.module.css */
   .light {
     background-color: white;
     color: black;
   }
   .dark {
     background-color: black;
     color: white;
   }
  1. 動的にクラスを適用
    状態に基づいてクラスを切り替えます。
   import styles from './themes.module.css';

   function App() {
     const [theme, setTheme] = useState('light');

     return (
       <div className={theme === 'light' ? styles.light : styles.dark}>
         <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
           Toggle Theme
         </button>
       </div>
     );
   }

仕組みの利点

  • 柔軟性:CSS Modulesにより、テーマごとのスタイルを効率的に分離管理できます。
  • 可読性:状態管理とスタイルが明確に分離され、コードが理解しやすくなります。
  • 拡張性:複数のテーマやテーマ間のトランジションを追加するのも容易です。

次のセクションでは、テーマごとの具体的なスタイル作成手順を詳しく解説します。

CSS Modulesによるテーマスタイルの作成


Reactアプリケーションでテーマスイッチングを実現するには、CSS Modulesを使用してテーマごとのスタイルを定義する必要があります。このセクションでは、ライトテーマとダークテーマのスタイルを作成する具体的な手順を解説します。

テーマ用CSS Modulesの作成


CSS Modulesを利用して、各テーマのスタイルを別々に定義します。以下はその例です:

themes.module.css:

/* ライトテーマ */
.light {
  background-color: #ffffff;
  color: #000000;
}

/* ダークテーマ */
.dark {
  background-color: #000000;
  color: #ffffff;
}

/* 共通のボタンスタイル */
.button {
  padding: 10px 20px;
  border-radius: 5px;
  font-size: 16px;
}

/* ライトテーマ用のボタンスタイル */
.lightButton {
  background-color: #f0f0f0;
  color: #333333;
  border: 1px solid #ccc;
}

/* ダークテーマ用のボタンスタイル */
.darkButton {
  background-color: #444444;
  color: #eeeeee;
  border: 1px solid #666666;
}

このように、ライトテーマとダークテーマで使用するスタイルを明確に分けます。

CSS Modulesをインポートして利用する


作成したCSS ModulesをReactコンポーネントにインポートして利用します。

ThemeSwitcher.js:

import React, { useState } from 'react';
import styles from './themes.module.css';

function ThemeSwitcher() {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };

  return (
    <div className={theme === 'light' ? styles.light : styles.dark}>
      <h1>React Theme Switcher</h1>
      <button
        className={theme === 'light' ? styles.lightButton : styles.darkButton}
        onClick={toggleTheme}
      >
        Toggle Theme
      </button>
    </div>
  );
}

export default ThemeSwitcher;

CSS Modulesでテーマスタイルを分離するメリット

  1. コードのモジュール化:ライトテーマとダークテーマのスタイルが分離されているため、変更や追加が容易です。
  2. クラス名の競合防止:CSS Modulesはクラス名をローカルスコープに限定するため、クラス名の衝突を防ぎます。
  3. 再利用性:テーマに関連しない共通スタイル(例:.button)を再利用することができます。

実装後の効果


これで、テーマごとのスタイルを切り替えられるようになり、アプリケーションのデザインをユーザーが選択可能になります。次のセクションでは、Reactコンポーネントでのテーマ切り替えロジックを詳しく説明します。

コンポーネントでテーマを切り替える方法


Reactでは、状態管理を利用してテーマを動的に切り替えることが可能です。このセクションでは、テーマスイッチングを実現するためのコンポーネント設計と、切り替え機能の実装手順を解説します。

テーマ切り替えの基本構造


Reactでテーマを切り替えるには、以下のような構造を構築します:

  1. 状態の管理useStateを使用して現在のテーマを保持します。
  2. 切り替え関数の作成:テーマをトグルする関数を定義します。
  3. 動的クラスの適用:現在のテーマに応じて適切なスタイルクラスを動的に適用します。

実装例


以下は、テーマスイッチング機能を持つReactコンポーネントの例です。

ThemeSwitcher.js:

import React, { useState } from 'react';
import styles from './themes.module.css';

function ThemeSwitcher() {
  // テーマを管理する状態
  const [theme, setTheme] = useState('light');

  // テーマを切り替える関数
  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };

  return (
    <div className={theme === 'light' ? styles.light : styles.dark}>
      <h1>{theme === 'light' ? 'Light Theme' : 'Dark Theme'}</h1>
      <button
        className={theme === 'light' ? styles.lightButton : styles.darkButton}
        onClick={toggleTheme}
      >
        Toggle Theme
      </button>
    </div>
  );
}

export default ThemeSwitcher;

ポイント解説

  1. 状態管理:
    useStateフックでthemeという状態を作成します。この状態が「light」または「dark」の文字列を保持します。
   const [theme, setTheme] = useState('light');
  1. トグル関数:
    現在のテーマに応じてthemeの値を切り替えます。lightならdarkに、darkならlightに変更します。
   const toggleTheme = () => {
     setTheme(theme === 'light' ? 'dark' : 'light');
   };
  1. 動的クラスの適用:
    themeの値に基づいてCSS Modulesのクラスを動的に適用します。以下のように条件演算子を使います:
   className={theme === 'light' ? styles.light : styles.dark}

実装後の挙動

  • ボタンをクリックするとテーマが切り替わり、背景色や文字色が動的に変更されます。
  • テーマの切り替えがスムーズで、状態に基づくスタイリングが反映されます。

拡張案

  1. アニメーション追加: 切り替え時にスムーズなトランジションを設定します。
  2. 複数テーマ対応: ライトとダーク以外のテーマを追加する設計に拡張可能です。
  3. テーマ切り替えトリガーの変更: 時間帯やシステム設定に基づいて自動的にテーマを切り替える機能も実装できます。

次のセクションでは、テーマ設定を永続化する方法について解説します。

テーマ設定の永続化


Reactアプリケーションでテーマスイッチングを実装した場合、ユーザーがページを再読み込みしても選択したテーマを維持することが求められます。これを実現するには、localStorageなどのブラウザストレージを利用してテーマ設定を永続化します。

localStorageを使用した永続化の仕組み


localStorageは、ブラウザにデータを保存し、リロードや再訪問時にそのデータを利用することができる仕組みです。テーマスイッチングでは、現在のテーマをlocalStorageに保存し、アプリケーションの初期化時にその値を読み取って設定を復元します。

実装手順

  1. テーマの保存
    テーマを切り替える際に、その値をlocalStorageに保存します。
  2. テーマの読み込み
    アプリケーションが初期化されたときにlocalStorageからテーマを取得し、適用します。

以下は実装例です:

ThemeSwitcher.js:

import React, { useState, useEffect } from 'react';
import styles from './themes.module.css';

function ThemeSwitcher() {
  // 初期テーマの設定(localStorageから取得)
  const [theme, setTheme] = useState(() => {
    return localStorage.getItem('theme') || 'light';
  });

  // テーマを切り替える関数
  const toggleTheme = () => {
    const newTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    localStorage.setItem('theme', newTheme); // localStorageに保存
  };

  // localStorageからのテーマ適用
  useEffect(() => {
    document.body.className = theme === 'light' ? styles.light : styles.dark;
  }, [theme]);

  return (
    <div className={theme === 'light' ? styles.light : styles.dark}>
      <h1>{theme === 'light' ? 'Light Theme' : 'Dark Theme'}</h1>
      <button
        className={theme === 'light' ? styles.lightButton : styles.darkButton}
        onClick={toggleTheme}
      >
        Toggle Theme
      </button>
    </div>
  );
}

export default ThemeSwitcher;

コードのポイント

  1. 初期化時のテーマ設定:
    useStateの初期値としてlocalStorage.getItem('theme')を取得します。localStorageに値がない場合は、デフォルトでlightテーマを設定します。
   const [theme, setTheme] = useState(() => {
     return localStorage.getItem('theme') || 'light';
   });
  1. テーマの保存:
    toggleTheme関数でテーマを切り替える際に、新しいテーマをlocalStorageに保存します。
   localStorage.setItem('theme', newTheme);
  1. テーマの適用:
    useEffectを使用して、テーマが変更されるたびにドキュメント全体のクラスを更新します。
   useEffect(() => {
     document.body.className = theme === 'light' ? styles.light : styles.dark;
   }, [theme]);

実装後の効果

  • ユーザーが選択したテーマがブラウザに保存され、リロードや再訪問時にその設定が復元されます。
  • スムーズで直感的なユーザー体験を提供します。

拡張案

  1. セッションベースの保存: セッションに限定してテーマを保存したい場合はsessionStorageを使用します。
  2. サーバーサイド保存: ユーザーアカウントがあるアプリケーションでは、テーマ設定をサーバーに保存することでデバイス間で同期できます。
  3. システムテーマの自動適用: 初期化時にシステムのカラーモード(例: ダークモード)を検出して初期テーマに設定する方法も考えられます。

次のセクションでは、Context APIを用いてテーマ管理をより効率的に行う方法を解説します。

コンテキストAPIによるテーマ管理


ReactのContext APIを使用すると、テーマスイッチングの状態やロジックをコンポーネントツリー全体で簡単に共有できます。これにより、複数のコンポーネントでテーマに関する情報を管理しやすくなります。

Context APIの仕組み


Context APIを利用してテーマ管理を行う場合、以下のような構造を構築します:

  1. テーマ情報を含むContextの作成: テーマの状態や切り替え関数を保持します。
  2. Providerによる状態の供給: アプリケーション全体または特定のコンポーネントツリーにテーマ情報を渡します。
  3. Consumerでのテーマ利用: 子コンポーネントがテーマ情報を参照し、動的なスタイリングやロジックに使用します。

実装手順

以下はContext APIを用いたテーマスイッチングの実装例です:

ThemeContext.js:

import React, { createContext, useState, useEffect } from 'react';

// テーマコンテキストの作成
export const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState(() => {
    return localStorage.getItem('theme') || 'light';
  });

  const toggleTheme = () => {
    const newTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    localStorage.setItem('theme', newTheme);
  };

  useEffect(() => {
    document.body.className = theme === 'light' ? 'light' : 'dark';
  }, [theme]);

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

App.js:

import React from 'react';
import { ThemeProvider } from './ThemeContext';
import ThemeSwitcher from './ThemeSwitcher';

function App() {
  return (
    <ThemeProvider>
      <ThemeSwitcher />
    </ThemeProvider>
  );
}

export default App;

ThemeSwitcher.js:

import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
import styles from './themes.module.css';

function ThemeSwitcher() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <div className={theme === 'light' ? styles.light : styles.dark}>
      <h1>{theme === 'light' ? 'Light Theme' : 'Dark Theme'}</h1>
      <button
        className={theme === 'light' ? styles.lightButton : styles.darkButton}
        onClick={toggleTheme}
      >
        Toggle Theme
      </button>
    </div>
  );
}

export default ThemeSwitcher;

コードのポイント

  1. テーマの状態管理を一元化:
    ThemeContextでテーマの状態と切り替えロジックを一元管理することで、コードの可読性と再利用性が向上します。
  2. Providerで状態を供給:
    ThemeProviderがツリー全体にテーマ情報を供給します。これにより、子コンポーネントはuseContextフックで簡単に情報を利用できます。
  3. Consumerでテーマ情報を活用:
    子コンポーネントはuseContextを使って、現在のテーマや切り替え関数を取得します。

Context APIを使用するメリット

  • グローバルな状態管理: テーマ情報をアプリケーション全体で共有でき、複数のコンポーネントで一貫性を保てます。
  • コードの簡潔化: 各コンポーネントでテーマロジックを記述する必要がなくなります。
  • スケーラビリティ: 大規模アプリケーションでも状態管理を効率的に行えます。

拡張案

  1. テーマごとのスタイルを動的にロード: 特定のテーマに応じたスタイルシートを遅延ロードする仕組みを導入します。
  2. 複数テーマ対応: lightdark以外のテーマを追加し、拡張性を高めます。
  3. サーバーとの連携: テーマ設定をサーバーサイドと同期し、アカウントごとに保存する機能を追加します。

次のセクションでは、実際に動作するReactアプリケーションのサンプルコードを提示し、さらなる理解を深めます。

実践コード例


テーマスイッチングを実装したReactアプリケーションの完全なサンプルコードを紹介します。この例では、CSS Modulesを使用してダークテーマとライトテーマを切り替える機能を実装し、localStorageによる永続化とContext APIによるテーマ管理を組み合わせています。

プロジェクト構成


以下のディレクトリ構造を使用します:

src/
├── App.js
├── ThemeContext.js
├── ThemeSwitcher.js
├── themes.module.css
└── index.js

コード

themes.module.css:

/* ライトテーマスタイル */
.light {
  background-color: #ffffff;
  color: #000000;
}

/* ダークテーマスタイル */
.dark {
  background-color: #000000;
  color: #ffffff;
}

/* ボタン共通スタイル */
.button {
  padding: 10px 20px;
  border-radius: 5px;
  font-size: 16px;
  cursor: pointer;
}

/* ライトテーマ用ボタン */
.lightButton {
  background-color: #f0f0f0;
  color: #000000;
  border: 1px solid #ccc;
}

/* ダークテーマ用ボタン */
.darkButton {
  background-color: #444444;
  color: #ffffff;
  border: 1px solid #666666;
}

ThemeContext.js:

import React, { createContext, useState, useEffect } from 'react';

// テーマコンテキストの作成
export const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState(() => {
    return localStorage.getItem('theme') || 'light';
  });

  const toggleTheme = () => {
    const newTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    localStorage.setItem('theme', newTheme);
  };

  useEffect(() => {
    document.body.className = theme;
  }, [theme]);

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

ThemeSwitcher.js:

import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
import styles from './themes.module.css';

function ThemeSwitcher() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <div className={theme === 'light' ? styles.light : styles.dark}>
      <h1>{theme === 'light' ? 'Light Theme' : 'Dark Theme'}</h1>
      <button
        className={theme === 'light' ? styles.lightButton : styles.darkButton}
        onClick={toggleTheme}
      >
        Toggle Theme
      </button>
    </div>
  );
}

export default ThemeSwitcher;

App.js:

import React from 'react';
import { ThemeProvider } from './ThemeContext';
import ThemeSwitcher from './ThemeSwitcher';

function App() {
  return (
    <ThemeProvider>
      <ThemeSwitcher />
    </ThemeProvider>
  );
}

export default App;

index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

動作確認

  1. アプリケーションを起動します(npm startまたはyarn start)。
  2. デフォルトではライトテーマが表示されます。
  3. ボタンをクリックすると、ダークテーマに切り替わります。
  4. ページをリロードしても、選択したテーマが保持されます。

サンプルアプリの特徴

  • Context APIを使用してテーマ情報をグローバルに管理。
  • localStorageを利用してテーマ設定を永続化。
  • CSS Modulesでスタイルをモジュール化し、競合を防止。
  • シンプルなコードでテーマスイッチングの仕組みを実現。

次のセクションでは、この実装をさらに応用する方法や、よくある課題とその解決策について解説します。

応用例と課題


CSS ModulesとReactを使用したテーマスイッチングの実装は、基本的な切り替え機能だけでなく、複数のテーマや高度なアニメーション効果を取り入れることで、よりリッチなユーザー体験を提供できます。一方で、いくつかの課題も存在します。このセクションでは応用例を紹介し、それに伴う課題と解決策を解説します。

応用例

1. 複数テーマの対応


現在はライトテーマとダークテーマの切り替えのみですが、他のテーマ(例:セピアモード、ハイコントラストテーマ)を追加することが可能です。

拡張例:

const themes = ['light', 'dark', 'sepia'];
const toggleTheme = () => {
  const currentIndex = themes.indexOf(theme);
  const nextIndex = (currentIndex + 1) % themes.length;
  setTheme(themes[nextIndex]);
  localStorage.setItem('theme', themes[nextIndex]);
};

CSS:

.sepia {
  background-color: #f5e6c8;
  color: #5b4636;
}

2. テーマ間のトランジションアニメーション


テーマ変更時にスムーズなアニメーションを追加することで、より洗練された見た目を提供できます。

CSSトランジションの追加:

.light, .dark, .sepia {
  transition: background-color 0.3s, color 0.3s;
}

JSXの変更は不要: この変更はCSSだけで動作します。

3. ユーザーのシステム設定に基づく初期テーマの適用


ユーザーのOSやブラウザの設定に従って、初期テーマを自動的に選択することも可能です。

初期テーマの設定例:

const [theme, setTheme] = useState(() => {
  const savedTheme = localStorage.getItem('theme');
  if (savedTheme) return savedTheme;
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
});

課題と解決策

1. テーマスタイルの肥大化


テーマが増えるとCSSファイルが大きくなり、管理が困難になります。これに対する解決策として、動的なスタイルシートのインポートが有効です。

解決策: 必要なテーマのスタイルのみを動的にロードします。

useEffect(() => {
  import(`./themes/${theme}.module.css`).then((module) => {
    setStyles(module.default);
  });
}, [theme]);

2. 複数のテーマ間で共通スタイルが重複する


テーマごとに共通スタイルが重複して記述される場合があります。

解決策: 共通スタイルを別のCSSファイルに切り出し、各テーマでインポートします。

/* common.module.css */
.button {
  padding: 10px 20px;
  border-radius: 5px;
}

/* light.module.css */
@import './common.module.css';
.light {
  background-color: #ffffff;
  color: #000000;
}

3. グローバル状態管理ツールとの競合


Context APIを使用している場合、ReduxやMobXなどの状態管理ライブラリと競合する可能性があります。

解決策: アプリ全体で統一的に状態管理を行うため、既存のツール(例:Redux)にテーマ管理を統合します。

// Reduxアクション
const toggleTheme = () => ({
  type: 'TOGGLE_THEME',
});

まとめ


応用例を活用することで、Reactアプリケーションのテーマスイッチングをさらに強化できます。一方、課題を適切に解決することで、拡張性や保守性を損なうことなく機能を追加できます。次のセクションでは、この記事のポイントを振り返り、まとめます。

まとめ


本記事では、ReactでCSS Modulesを活用したテーマスイッチングの実装手順を詳しく解説しました。以下が主なポイントです:

  1. CSS Modulesの利点:クラス名の競合を防ぎつつ、テーマごとのスタイルを効率的に管理。
  2. テーマスイッチングの実装:Reactの状態管理とCSS Modulesを組み合わせ、直感的にテーマを切り替え可能に。
  3. localStorageによる永続化:ユーザーの選択を記憶し、再訪時も設定を維持。
  4. Context APIの活用:テーマ情報をコンポーネントツリー全体で一元管理し、コードの可読性と拡張性を向上。
  5. 応用例と課題の解決:複数テーマ対応、アニメーション追加、システムテーマの自動適用など、高度な機能の実装。

これらを活用することで、ユーザー体験を向上させつつ、スケーラブルなテーマ管理を実現できます。Reactアプリケーションの開発におけるテーマスイッチングは、柔軟性と拡張性を兼ね備えた重要な要素です。今回の内容を基に、ぜひ独自のテーマスイッチング機能を構築してみてください。

コメント

コメントする

目次