カテゴリ: React 更新日: 2026/03/26

ReactのContext APIでダークモードを実装!テーマ切り替えの方法を初心者向けに完全解説

Contextを使ったテーマ切り替え(ダークモード実装)
Contextを使ったテーマ切り替え(ダークモード実装)

先生と生徒の会話形式で理解しよう

生徒

「Reactでダークモードとライトモードをボタンで切り替える機能を作りたいんですが、どうすればいいですか?」

先生

「テーマの切り替えはContext APIで管理するのがとても向いています。テーマ情報はアプリ全体のコンポーネントで使う必要があるので、Contextで一元管理すると便利ですよ。」

生徒

「ページをリロードしてもダークモードを維持する方法もありますか?」

先生

「localStorageと組み合わせれば、ブラウザを閉じても設定を保持できます。順番に実装していきましょう!」

1. ダークモードとは?テーマ切り替え機能の概要を理解しよう

1. ダークモードとは?テーマ切り替え機能の概要を理解しよう
1. ダークモードとは?テーマ切り替え機能の概要を理解しよう

最近のWebサービスやアプリでは、画面を暗い配色に切り替える「ダークモード」が広く使われています。目の疲れを軽減したり、暗い環境での使用を快適にしたりする効果があり、ユーザーに好まれる機能のひとつです。逆に明るい配色の画面を「ライトモード」といいます。

Reactでこのテーマ切り替え機能を実装するとき、テーマの状態(ダークかライトか)はアプリ全体で共有する必要があります。ヘッダー、サイドバー、コンテンツエリア、フッターなど、すべてのコンポーネントがテーマに合わせた色で表示される必要があるためです。

このような「アプリ全体で共有するデータ」の管理にはContext APIが最適です。テーマ用のContextを作り、アプリ全体をProviderで囲んでおくと、どのコンポーネントからでも現在のテーマを取得・変更できるようになります。

2. ThemeContextを作成してテーマ情報を管理しよう

2. ThemeContextを作成してテーマ情報を管理しよう
2. ThemeContextを作成してテーマ情報を管理しよう

まずはテーマ管理専用のContextファイルを作成します。ここではThemeContext.jsxという名前でファイルを作り、Contextの定義とProviderコンポーネントをまとめます。


// ThemeContext.jsx
import { createContext, useContext, useState } from "react";

// テーマ用のContextを作成
export const ThemeContext = createContext(null);

// カスタムフック:useContextをラップして使いやすくする
export function useTheme() {
  return useContext(ThemeContext);
}

// Providerコンポーネント
export function ThemeProvider({ children }) {
  const [isDark, setIsDark] = useState(false);

  const toggleTheme = () => {
    setIsDark((prev) => !prev);
  };

  // テーマに応じた色をオブジェクトにまとめる
  const theme = {
    isDark,
    toggleTheme,
    colors: {
      background: isDark ? "#1a1a2e" : "#f8f9fa",
      surface: isDark ? "#16213e" : "#ffffff",
      text: isDark ? "#e0e0e0" : "#212529",
      subText: isDark ? "#a0a0a0" : "#6c757d",
      border: isDark ? "#444" : "#dee2e6",
    },
  };

  return (
    <ThemeContext.Provider value={theme}>
      {children}
    </ThemeContext.Provider>
  );
}
このファイル単体では画面に何も表示されません。ThemeProvider、useTheme、ThemeContextの3つをエクスポートしておくことで、アプリのどのファイルからでもインポートして使えるようになります。

カスタムフックとは、useContextなどのReactフックをラップして独自に作った関数のことです。useTheme()と短く書くだけでテーマのデータを取得できるようになるため、毎回useContext(ThemeContext)と書く手間が省けます。

3. Appコンポーネントにプロバイダーを設定してテーマを適用しよう

3. Appコンポーネントにプロバイダーを設定してテーマを適用しよう
3. Appコンポーネントにプロバイダーを設定してテーマを適用しよう

作成したThemeProviderをApp.jsxで読み込み、アプリ全体を囲みます。こうすることでアプリ内のすべてのコンポーネントがテーマ情報にアクセスできます。


// App.jsx
import React from "react";
import { ThemeProvider, useTheme } from "./ThemeContext";

function Header() {
  const { isDark, toggleTheme, colors } = useTheme();

  return (
    <header style={{
      background: colors.surface,
      color: colors.text,
      borderBottom: "1px solid " + colors.border,
      padding: "12px 20px",
      display: "flex",
      justifyContent: "space-between",
      alignItems: "center"
    }}>
      <span style={{ fontWeight: "bold" }}>マイサイト</span>
      <button
        onClick={toggleTheme}
        style={{ background: "transparent", border: "1px solid " + colors.border, color: colors.text, padding: "6px 14px", borderRadius: "6px", cursor: "pointer" }}
      >
        <i class={isDark ? "bi bi-sun-fill" : "bi bi-moon-fill"}></i>
        {isDark ? " ライトモード" : " ダークモード"}
      </button>
    </header>
  );
}

function MainContent() {
  const { colors } = useTheme();

  return (
    <main style={{ background: colors.background, color: colors.text, minHeight: "200px", padding: "20px" }}>
      <h2 style={{ color: colors.text }}>コンテンツエリア</h2>
      <p style={{ color: colors.subText }}>テーマに合わせて色が変わります。</p>
    </main>
  );
}

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

export default App;
画面上部にヘッダーが白背景で表示され、「ダークモード」ボタンが見えます。ボタンを押すと背景が濃い紺色に変わり、文字色も明るくなります。もう一度押すと元のライトモードに戻ります。HeaderとMainContentの両方が同時にテーマを切り替えています。

ThemeProviderがHeaderとMainContentの両方を囲んでいるため、どちらのコンポーネントもuseThemeでテーマ情報を取得できています。ボタンを押したときにtoggleThemeが呼ばれ、isDarkの値が切り替わると、Contextを使っているすべてのコンポーネントが自動的に再描画されます。

4. localStorageでダークモードの設定をブラウザに保存しよう

4. localStorageでダークモードの設定をブラウザに保存しよう
4. localStorageでダークモードの設定をブラウザに保存しよう

現状ではページをリロードするとダークモードの設定がリセットされてしまいます。localStorage(ローカルストレージ)を使うと、ブラウザにデータを保存して、ページをリロードしても設定を維持できるようになります。localStorageとはブラウザが持つ小さなデータ保存領域のことで、シンプルな文字列のデータを保存しておくことができます。


// ThemeContext.jsx(localStorage対応版)
import { createContext, useContext, useState, useEffect } from "react";

export const ThemeContext = createContext(null);

export function useTheme() {
  return useContext(ThemeContext);
}

export function ThemeProvider({ children }) {
  // localStorageから保存済みのテーマを読み込む(なければfalse)
  const [isDark, setIsDark] = useState(() => {
    const saved = localStorage.getItem("theme");
    return saved === "dark";
  });

  const toggleTheme = () => {
    setIsDark((prev) => !prev);
  };

  // isDarkが変わるたびにlocalStorageに保存する
  useEffect(() => {
    localStorage.setItem("theme", isDark ? "dark" : "light");
  }, [isDark]);

  const theme = {
    isDark,
    toggleTheme,
    colors: {
      background: isDark ? "#1a1a2e" : "#f8f9fa",
      surface: isDark ? "#16213e" : "#ffffff",
      text: isDark ? "#e0e0e0" : "#212529",
      border: isDark ? "#444" : "#dee2e6",
    },
  };

  return (
    <ThemeContext.Provider value={theme}>
      {children}
    </ThemeContext.Provider>
  );
}
ダークモードに切り替えた状態でページをリロードしても、ダークモードが維持されます。localStorageに「dark」または「light」という文字列が保存されており、次回起動時に読み込まれます。

useStateの引数に関数を渡す書き方(useState(() => {...}))を遅延初期化といいます。コンポーネントが最初に表示されるタイミングだけ初期値の計算が行われるため、localStorageへのアクセスを一度だけに抑えられます。useEffectはisDarkが変わるたびにlocalStorageへの保存処理を実行します。

5. OSのダークモード設定に自動で合わせる方法

5. OSのダークモード設定に自動で合わせる方法
5. OSのダークモード設定に自動で合わせる方法

スマートフォンやパソコンのOSには、システム全体をダークモードにする設定があります。この設定をReactで検知して、アプリのテーマを自動的に合わせることもできます。

JavaScriptにはwindow.matchMediaというAPIがあり、OSのカラーモード設定を取得できます。APIとは、プログラムから別の機能やサービスを利用するための窓口のようなものです。


import { createContext, useContext, useState, useEffect } from "react";

export const ThemeContext = createContext(null);

export function useTheme() {
  return useContext(ThemeContext);
}

export function ThemeProvider({ children }) {
  const [isDark, setIsDark] = useState(() => {
    // localStorageに設定があればそちらを優先
    const saved = localStorage.getItem("theme");
    if (saved) return saved === "dark";
    // なければOSの設定に合わせる
    return window.matchMedia("(prefers-color-scheme: dark)").matches;
  });

  const toggleTheme = () => setIsDark((prev) => !prev);

  useEffect(() => {
    localStorage.setItem("theme", isDark ? "dark" : "light");
  }, [isDark]);

  const theme = {
    isDark,
    toggleTheme,
    bg: isDark ? "#1a1a2e" : "#f8f9fa",
    text: isDark ? "#e0e0e0" : "#212529",
  };

  return (
    <ThemeContext.Provider value={theme}>
      {children}
    </ThemeContext.Provider>
  );
}
OSの設定がダークモードになっている場合、初回アクセス時からダークモードで表示されます。localStorageに保存済みの設定がある場合はそちらが優先されます。ユーザーが手動で切り替えた設定はlocalStorageに保存されるため、次回も維持されます。

window.matchMedia("(prefers-color-scheme: dark)").matchesがtrueのとき、OSがダークモードに設定されていることを意味します。localStorageに保存済みの設定があればそちらを優先し、なければOSの設定を初期値として使う、という優先順位になっています。

6. カードコンポーネントにテーマを適用してUIを仕上げよう

6. カードコンポーネントにテーマを適用してUIを仕上げよう
6. カードコンポーネントにテーマを適用してUIを仕上げよう

実際のアプリでは複数のコンポーネントが同じテーマ情報を使います。カードコンポーネントを追加してテーマが正しく適用されることを確認してみましょう。


// App.jsx
import React from "react";
import { ThemeProvider, useTheme } from "./ThemeContext";

function ThemeToggleButton() {
  const { isDark, toggleTheme } = useTheme();
  return (
    <button onClick={toggleTheme} class="btn btn-outline-secondary mb-4">
      <i class={isDark ? "bi bi-sun-fill" : "bi bi-moon-fill"}></i>
      {isDark ? " ライトモードに切り替え" : " ダークモードに切り替え"}
    </button>
  );
}

function ArticleCard({ title, description }) {
  const { colors } = useTheme();
  return (
    <div style={{
      background: colors.surface,
      color: colors.text,
      border: "1px solid " + colors.border,
      borderRadius: "8px",
      padding: "16px",
      marginBottom: "12px"
    }}>
      <h3 style={{ color: colors.text, fontSize: "1rem", marginBottom: "8px" }}>{title}</h3>
      <p style={{ color: colors.subText, fontSize: "0.9rem", margin: 0 }}>{description}</p>
    </div>
  );
}

function App() {
  return (
    <ThemeProvider>
      <div style={{ padding: "20px" }}>
        <ThemeToggleButton />
        <ArticleCard title="React入門" description="Reactの基本的な使い方を学ぼう" />
        <ArticleCard title="Context APIの使い方" description="グローバルな状態管理をマスターしよう" />
        <ArticleCard title="ダークモードの実装" description="テーマ切り替えをContext APIで管理する" />
      </div>
    </ThemeProvider>
  );
}

export default App;
3枚のカードが表示され、「ダークモードに切り替え」ボタンを押すと、すべてのカードの背景色・文字色・枠線の色が同時に切り替わります。どのカードも同じThemeContextからテーマ情報を取得しているため、一度の切り替えで全体が更新されます。

このように、Context APIでテーマを管理することで新しいコンポーネントを追加するたびにpropsを渡す必要がなく、useTheme()を呼び出すだけでテーマに対応できます。アプリが大きくなっても、テーマの管理場所はThemeContext.jsxのひとつだけです。

7. テーマ切り替えをさらに使いやすくするための工夫

7. テーマ切り替えをさらに使いやすくするための工夫
7. テーマ切り替えをさらに使いやすくするための工夫

ここまでで基本的なダークモードの実装は完成しました。実際の開発でよく見られる追加の工夫もいくつか紹介します。

まず、CSSカスタムプロパティ(CSS変数)との組み合わせです。CSSカスタムプロパティとは、CSSの中で変数のように使える値のことです。Reactのstyle属性でインラインスタイルを書く代わりに、テーマが変わったときにCSSカスタムプロパティの値を書き換えることで、CSSファイルでスタイルを管理しながらテーマ切り替えができます。コンポーネントの数が多くなるほど、この方法のほうがスタイルを一か所にまとめられるメリットが大きくなります。

次に、切り替えアニメーションを追加する方法です。CSSのtransitionプロパティを使うと、テーマが切り替わるときに色がなめらかに変化するアニメーションを付けられます。transition: background 0.3s, color 0.3sのようにスタイルに追加するだけで、ぱっと切り替わるより自然な印象になります。

また、テーマの種類を増やす方法もあります。ダークとライトの2択だけでなく、「セピア」「ハイコントラスト」など複数のテーマを選べるようにすることも可能です。isDarkというboolean値(真偽値)の代わりに、テーマ名を文字列で管理するよう変更するだけで対応できます。Context APIの設計をしっかり作っておくと、こうした拡張が比較的スムーズに行えます。

カテゴリの一覧へ
新着記事
New1
React
ReactのProps Drillingとは?コンテキストAPIで解決する方法を初心者向けに徹底解説
New2
React
Reactのイベントオブジェクト(event)の使い方とよく使うプロパティを初心者向けに解説
New3
React
ReactのContext APIでダークモードを実装!テーマ切り替えの方法を初心者向けに完全解説
New4
React
Reactにおけるイベント処理の基本を初心者向けに解説
人気記事
No.1
Java&Spring記事人気No1
React
Reactとは?初心者でもわかるReact.jsの基本概念と特徴をやさしく解説
No.2
Java&Spring記事人気No2
React
React開発におすすめのVSCode拡張機能まとめ!初心者でもすぐ使える便利ツール紹介
No.3
Java&Spring記事人気No3
React
ReactとTypeScriptの環境構築をやさしく解説!Viteとtsconfigの設定も丁寧に紹介
No.4
Java&Spring記事人気No4
React
React(TypeScript)で学ぶStateの型推論と型指定の違い!型安全なState管理を初心者向けにやさしく解説
No.5
Java&Spring記事人気No5
React
Reactでキーボードイベントを活用する方法!onKeyDown, onKeyUp, onKeyPressを初心者向けに解説
No.6
Java&Spring記事人気No6
Next.js
Next.jsのRoute Groupの使い方を完全ガイド!App Routerでフォルダ構成を整理する方法
No.7
Java&Spring記事人気No7
React
ReactのPresentational Componentを完全ガイド!初心者でもわかるStateを持たないコンポーネントの特徴
No.8
Java&Spring記事人気No8
React
Reactでネストしたコンポーネントにデータを渡す方法を完全解説!Context APIで深い階層も簡単に