2026年6月1日
2026年6月1日
WordPressにダークモード切り替えスイッチを実装する方法(CSS変数+localStorage)
WordPressのサイトにダークモードの切り替えボタンを付けるには、色をCSS変数にまとめ、<html> 要素の属性を書き換えて配色を切り替え、選んだ状態をブラウザのlocalStorageに保存するのが扱いやすい方法です。この記事では、子テーマに数十行を追加するだけで動く実装と、ページを開いた瞬間に白く光る「ちらつき」の防ぎ方、キャッシュとの相性までを説明します。
実装の考え方
ダークモードの切り替えで決めることは、次の3つです。
- 配色をどう切り替えるか:背景や文字の色をCSS変数(カスタムプロパティ)で定義し、
<html data-theme="dark">のときだけ変数の値を差し替えます。個々の要素に暗い色を書き足していく方式より、修正箇所が少なく済みます。 - 最初にどちらで表示するか:利用者が以前選んだ設定があればそれを使い、なければOSの設定(
prefers-color-scheme)に合わせます。 - 選んだ状態をどこに保存するか:ブラウザのlocalStorageに保存します。サーバー側で保存するとページキャッシュと衝突しやすいためです(理由は後述)。
作業は子テーマで行います。親テーマの functions.php を直接編集すると、テーマの更新で消えてしまいます。編集前にはテーマファイルのバックアップを取っておいてください。
ファイル構成は次のようにします。
wp-content/themes/my-child-theme/
├── functions.php
├── css/dark-mode.css
└── js/dark-mode.js
手順1:表示前にテーマを決める(ちらつき防止)
いちばんつまずきやすいのがここです。切り替え用のJavaScriptをフッターで読み込むと、ページはいったん明るい配色で描画され、その後で暗く切り替わります。ダークモードを選んだ利用者には、ページを開くたびに一瞬白く光って見えます。
これを避けるため、<head> の中でごく短いスクリプトを先に実行し、描画が始まる前に data-theme 属性を付けておきます。
<?php
// functions.php(子テーマ)
// 描画前に data-theme を決める。localStorage が使えない環境では OS の設定に従う
add_action( 'wp_head', function () {
$js = <<<'JS'
(function () {
var t = null;
try { t = localStorage.getItem('color-theme'); } catch (e) {}
if (t !== 'dark' && t !== 'light') {
t = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
document.documentElement.setAttribute('data-theme', t);
})();
JS;
wp_print_inline_script_tag( $js );
}, 1 );
wp_print_inline_script_tag() は、WordPressが <script> タグを組み立てて出力する関数です。echo で直接タグを書くより、セキュリティ系プラグインがスクリプトに属性を加える場合などにも対応しやすくなります。優先度を 1 にしているのは、<head> のなるべく早い位置で実行させるためです。
try で囲んでいるのは、ブラウザの設定やプライベートモードによってはlocalStorageへのアクセスで例外が出るからです。例外が出ても、OSの設定に合わせた表示にはなります。
手順2:色をCSS変数で定義する
css/dark-mode.css に、明るい配色と暗い配色を変数として書きます。
/* 明るい配色(初期値) */
:root {
--c-bg: #ffffff;
--c-text: #1f2328;
--c-link: #0b57d0;
--c-surface: #f4f5f7;
--c-border: #d0d4da;
color-scheme: light;
}
/* 暗い配色 */
:root[data-theme="dark"] {
--c-bg: #121417;
--c-text: #e3e5e8;
--c-link: #8ab4f8;
--c-surface: #1d2025;
--c-border: #3a3f47;
color-scheme: dark;
}
body {
background-color: var(--c-bg);
color: var(--c-text);
}
a { color: var(--c-link); }
/* 切り替えボタン */
.theme-toggle {
position: fixed;
right: 16px;
bottom: 16px;
z-index: 1000;
width: 44px;
height: 44px;
border: 1px solid var(--c-border);
border-radius: 50%;
background: var(--c-surface);
color: var(--c-text);
font-size: 20px;
cursor: pointer;
}
.theme-toggle__icon::before { content: "\263E"; } /* 月 */
:root[data-theme="dark"] .theme-toggle__icon::before { content: "\2600"; } /* 太陽 */
color-scheme を指定すると、フォームの入力欄やスクロールバーなど、ブラウザが描く部品も暗い配色に合わせてくれます。
暗い背景は真っ黒(#000)より、少し明るい濃いグレーにしたほうが文字のにじみが目立ちにくくなります。文字と背景のコントラストは、ブラウザの開発者ツールで確認できます。
既存のテーマはたいてい色を直接指定しているため、body を変えただけではヘッダーやサイドバー、ボタンが明るいまま残ります。ダークモードで表示してみて、浮いて見える部分を開発者ツールで調べ、その要素の色を変数に置き換えていく、という進め方になります。
手順3:ボタンを置き、クリックで切り替える
ボタンのHTMLは wp_body_open フックで <body> の直後に出力します。
// functions.php(子テーマ) 続き(先頭の <?php は不要)
add_action( 'wp_body_open', function () {
?>
<button type="button" class="theme-toggle" aria-pressed="false"
aria-label="<?php echo esc_attr__( 'ダークモード', 'my-child-theme' ); ?>">
<span class="theme-toggle__icon" aria-hidden="true"></span>
</button>
<?php
} );
aria-pressed は、読み上げソフトに「押されている/押されていない」を伝える属性です。ダークモードのときに true になるよう、JavaScriptで更新します。
js/dark-mode.js は次のとおりです。
(function () {
var root = document.documentElement;
var buttons = document.querySelectorAll('.theme-toggle');
function apply(theme) {
root.setAttribute('data-theme', theme);
buttons.forEach(function (b) {
b.setAttribute('aria-pressed', theme === 'dark' ? 'true' : 'false');
});
}
// 手順1で決めた状態をボタン表示に反映
apply(root.getAttribute('data-theme') === 'dark' ? 'dark' : 'light');
buttons.forEach(function (b) {
b.addEventListener('click', function () {
var next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
apply(next);
try { localStorage.setItem('color-theme', next); } catch (e) {}
});
});
})();
最後に、CSSとJavaScriptを読み込みます。
// functions.php(子テーマ) 続き(先頭の <?php は不要)
add_action( 'wp_enqueue_scripts', function () {
$dir = get_stylesheet_directory();
$uri = get_stylesheet_directory_uri();
wp_enqueue_style(
'my-dark-mode',
$uri . '/css/dark-mode.css',
array(),
filemtime( $dir . '/css/dark-mode.css' )
);
wp_enqueue_script(
'my-dark-mode',
$uri . '/js/dark-mode.js',
array(),
filemtime( $dir . '/js/dark-mode.js' ),
array( 'in_footer' => true )
);
}, 20 );
子テーマなので get_stylesheet_directory_uri() を使います。get_template_directory_uri() は親テーマのディレクトリを指すため、ここで使うとファイルが見つかりません。バージョンに filemtime() を渡しておくと、ファイルを更新したときにブラウザの古いキャッシュが使われにくくなります。優先度を 20 にしているのは、親テーマのCSSより後に読み込ませて上書きしやすくするためです。
動作を確認する
次の順で確かめると、取りこぼしが見つかりやすくなります。
- ボタンを押して配色が切り替わり、ページを移動しても選んだ状態が続くか
- ダークモードのままページを再読み込みしたとき、白く光らないか
- 開発者ツールのApplication(アプリケーション)タブでlocalStorageの
color-themeを削除し、OSの外観設定に合わせて表示されるか - キーボードのTabキーでボタンにフォーカスが移り、Enterキーで切り替えられるか
wp_body_open で出したボタンが表示されない場合は、テーマの header.php に wp_body_open() の呼び出しがない可能性があります。その場合は、テーマのヘッダー部分にボタンのHTMLを直接書くか、カスタムHTMLブロックで置いてください。
キャッシュと保存先の関係
「ログイン中のユーザーはユーザーメタに保存し、PHP側で <html> にクラスを付ける」という方法もありますが、ページキャッシュを使っているサイトでは問題が起きます。最初にアクセスした人の配色でHTMLがキャッシュされ、別の人にもその配色が配信されてしまうためです。
localStorageを使う方式では、サーバーが返すHTMLは全員同じで、配色の判断はブラウザ側だけで行います。キャッシュ系プラグインやCDNをそのまま使えるのが利点です。
ログイン会員向けに端末をまたいで設定を共有したい場合は、REST APIやAjaxで保存する処理を追加することになります。そのときはnonceの検証と current_user_can() による権限チェック、受け取った値が dark か light のどちらかであることの確認を省かないでください。
画像やブロックテーマでの注意点
ロゴや図解の画像は、背景が透明だと暗い背景で見えなくなることがあります。暗い配色用の画像に差し替えるか、ロゴの周りに明るい背景を敷いて対応します。写真に filter: brightness() をかけて暗くする方法もありますが、色味が変わるので使う範囲は絞ったほうが無難です。
ブロックテーマでは、配色は主に theme.json で管理されます。theme.json の色はCSS変数(--wp--preset--color--〇〇)として出力されるので、:root[data-theme="dark"] の中でそれらの変数を上書きすれば、同じ仕組みで切り替えられます。変数名はテーマによって異なるため、開発者ツールで確認してください。
自分で実装せずにプラグインで済ませる選択肢もあります。WordPress.orgには「WP Dark Mode」などダークモード用のプラグインがあり、テーマの色を自動で反転させる方式のものが多くあります。手軽な反面、細かい色の調整は自前の実装のほうが行いやすく、配色を作り込みたいサイトでは本記事の方法が向いています。対応状況や機能はプラグインの更新で変わるため、導入時に最新の説明を確認してください(2026年10月現在)。
まとめ
- 色はCSS変数にまとめ、
<html data-theme="dark">のときだけ値を差し替えます。 - ちらつきを防ぐには、
<head>の早い位置でdata-themeを決める短いスクリプトを実行します。 - 選んだ状態はlocalStorageに保存すると、ページキャッシュやCDNと衝突しません。
- ボタンには
aria-pressedを付け、キーボードでも操作できることを確かめます。 - サーバー側に保存する場合は、nonce・権限チェック・値の検証を組み込みます。