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でスクリーンリーダーにも状態を伝える
実装手順
- CSS変数で色を管理する
- functions.phpでCSSとJavaScriptを読み込む
- wp_headで初期テーマを先に反映する
- header.phpなどに切り替えボタンを置く
- 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でユーザーの選択を保存する構成にすると扱いやすくなります。重要なのは「黒くすること」ではなく、ライトでもダークでも読みやすい設計にすることです。