HOME / BLOG
BLOG

WordPressオリジナルテーマにダークモードを実装する方法【CSS・JavaScript】

デフォルト画像

WordPressオリジナルテーマを作っていると、あとから「ダークモードも欲しい」と思うことがあります。ただ、単純に背景を黒くするだけでは、ロゴ、カード、リンク、フォーム、コードブロックなどの色が崩れやすく、読みにくい画面になりがちです。

この記事では、CSS変数、prefers-color-scheme、localStorage、アクセシビリティを組み合わせて、WordPressテーマに実用的なダークモードを実装する方法をまとめます。

SEOタイトル

WordPressオリジナルテーマにダークモードを実装する方法【CSS・JavaScript】

meta description

WordPressオリジナルテーマにダークモードを実装する方法を、CSS変数、prefers-color-scheme、localStorage、切り替えボタン付きで解説します。

完成イメージ

  • 初回表示ではOS設定に合わせてライト/ダークを自動判定する
  • ユーザーがボタンを押したらテーマを切り替える
  • 選択したテーマをlocalStorageに保存する
  • 次回アクセス時は保存したテーマを優先する
  • aria-labelとaria-pressedでスクリーンリーダーにも状態を伝える

実装手順

  1. CSS変数で色を管理する
  2. functions.phpでCSSとJavaScriptを読み込む
  3. wp_headで初期テーマを先に反映する
  4. header.phpなどに切り替えボタンを置く
  5. JavaScriptでクリック・OS設定変更・localStorageを扱う

PHP: CSSとJavaScriptを読み込む

まずはテーマ側でCSSとJavaScriptを読み込みます。さらに、ページが表示される前に現在のテーマを反映する短いスクリプトをwp_headへ出力します。これを入れておくと、読み込み直後に白背景が一瞬見えるちらつきを抑えられます。

/**
 * ダークモード用のCSS・JSを読み込む。
 */
add_action('wp_enqueue_scripts', function (): void {
    $theme_uri = get_template_directory_uri();
    $theme_dir = get_template_directory();

    wp_enqueue_style(
        'theme-style',
        $theme_uri . '/style.css',
        [],
        filemtime($theme_dir . '/style.css')
    );

    wp_enqueue_script(
        'theme-color-mode',
        $theme_uri . '/js/theme-color-mode.js',
        [],
        filemtime($theme_dir . '/js/theme-color-mode.js'),
        true
    );
});

/**
 * 初回描画前にテーマを反映して、ちらつきを抑える。
 */
add_action('wp_head', function (): void {
    ?>
    <script>
      (function () {
        try {
          var key = 'nines-theme';
          var saved = localStorage.getItem(key);
          var system = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
          var theme = saved === 'light' || saved === 'dark' ? saved : system;

          document.documentElement.dataset.theme = theme;
          document.documentElement.style.colorScheme = theme;
        } catch (error) {
          document.documentElement.dataset.theme = 'light';
        }
      }());
    </script>
    <?php
}, 0);

PHP: 切り替えボタンを配置する

ボタンはheader.phpなど、全ページで表示される場所に置くのが扱いやすいです。見た目のアイコンだけに頼らず、aria-labelとaria-pressedで現在の意味を伝えます。

<button
  class="theme-toggle"
  type="button"
  data-theme-toggle
  aria-label="ダークモードに切り替える"
  aria-pressed="false"
>
  <span class="theme-toggle__icon" aria-hidden="true"></span>
  <span class="theme-toggle__text" data-theme-toggle-label>Dark</span>
</button>

CSS: :rootで色を管理する

ダークモードでは、各要素に直接色を書くより、:rootに色の役割を定義しておく方が保守しやすくなります。背景色、本文色、薄い文字色、境界線、アクセント色を変数化しておくと、あとから調整しやすいです。

:root {
  color-scheme: light;
  --color-bg: #ffffff;
  --color-surface: #f6f7f9;
  --color-text: #141414;
  --color-muted: #5f6672;
  --color-border: #d9dee7;
  --color-accent: #e60033;
  --color-accent-text: #ffffff;
  --shadow-card: 0 16px 40px rgb(15 23 42 / 0.08);
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --color-bg: #111318;
  --color-surface: #1b1f27;
  --color-text: #f7f8fb;
  --color-muted: #b8bfcc;
  --color-border: #343b49;
  --color-accent: #ff4d73;
  --color-accent-text: #111318;
  --shadow-card: 0 18px 48px rgb(0 0 0 / 0.32);
}

body {
  background: var(--color-bg);
  color: var(--color-text);
  transition: background-color .2s ease, color .2s ease;
}

a {
  color: var(--color-accent);
}

.site-header,
.post-card,
.sidebar-widget {
  background: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-card);
}

.post-meta,
.post-excerpt {
  color: var(--color-muted);
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
  padding: 8px 12px;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  cursor: pointer;
}

.theme-toggle:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--color-accent) 45%, transparent);
  outline-offset: 3px;
}

.theme-toggle__icon {
  inline-size: 18px;
  block-size: 18px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: inset -6px -4px 0 var(--color-surface);
}

:root[data-theme="dark"] .theme-toggle__icon {
  box-shadow: inset 6px 4px 0 var(--color-surface);
}

@media (prefers-reduced-motion: reduce) {
  body {
    transition: none;
  }
}

JavaScript: localStorageとOS設定に対応する

JavaScriptでは、保存済みのテーマがあればそれを優先し、なければOS設定を使います。ユーザーがボタンを押した場合はlocalStorageへ保存し、以後はその選択を優先します。

(function () {
  var STORAGE_KEY = 'nines-theme';
  var root = document.documentElement;
  var mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
  var toggle = document.querySelector('[data-theme-toggle]');
  var label = document.querySelector('[data-theme-toggle-label]');

  function getStoredTheme() {
    try {
      var value = localStorage.getItem(STORAGE_KEY);
      return value === 'light' || value === 'dark' ? value : null;
    } catch (error) {
      return null;
    }
  }

  function setStoredTheme(theme) {
    try {
      localStorage.setItem(STORAGE_KEY, theme);
    } catch (error) {
      // localStorageが使えない環境では、現在のページ内だけで切り替える。
    }
  }

  function getSystemTheme() {
    return mediaQuery.matches ? 'dark' : 'light';
  }

  function updateButton(theme) {
    if (!toggle || !label) {
      return;
    }

    var isDark = theme === 'dark';
    toggle.setAttribute('aria-pressed', String(isDark));
    toggle.setAttribute(
      'aria-label',
      isDark ? 'ライトモードに切り替える' : 'ダークモードに切り替える'
    );
    label.textContent = isDark ? 'Light' : 'Dark';
  }

  function applyTheme(theme) {
    root.dataset.theme = theme;
    root.style.colorScheme = theme;
    updateButton(theme);
  }

  applyTheme(getStoredTheme() || getSystemTheme());

  if (toggle) {
    toggle.addEventListener('click', function () {
      var nextTheme = root.dataset.theme === 'dark' ? 'light' : 'dark';
      applyTheme(nextTheme);
      setStoredTheme(nextTheme);
    });
  }

  mediaQuery.addEventListener('change', function () {
    if (!getStoredTheme()) {
      applyTheme(getSystemTheme());
    }
  });
}());

改善ポイント

  • ロゴがSVGならcurrentColorで色を変えられる形にしておく
  • 画像は暗くしすぎず、必要な場所だけfilterや別画像で調整する
  • フォーム、テーブル、コードブロックの配色も必ず確認する
  • prefers-reduced-motionに対応して、不要なアニメーションを抑える
  • 背景と文字のコントラストを実機で確認する

まとめ

WordPressオリジナルテーマのダークモードは、CSS変数で色を整理し、初期描画前にテーマを反映し、JavaScriptでユーザーの選択を保存する構成にすると扱いやすくなります。重要なのは「黒くすること」ではなく、ライトでもダークでも読みやすい設計にすることです。

内部リンク案