How to Add Dark Mode Support to WordPress Without Plugins
Dark mode is no longer a “nice to have”.
Modern operating systems expose a user preference for light or dark color schemes,
and browsers make this preference available via CSS and JavaScript.
This article explains how to add dark mode support to a WordPress theme using code only,
without plugins, without bloated toggles, and without breaking performance or accessibility.
How Dark Mode Works on the Web
Browsers expose the user’s OS-level preference via:
- CSS media query:
prefers-color-scheme - JavaScript:
window.matchMedia()
Valid values are:
lightdark
A WordPress theme should:
- Respect the system preference by default
- Use CSS variables for maintainability
- Optionally allow a manual override (stored locally)
Recommended Architecture
The cleanest approach is:
- Define color tokens using CSS custom properties
- Override them in
prefers-color-scheme: dark - Optionally add a class-based override for a toggle
This avoids duplicating entire stylesheets.
1) Define Color Tokens with CSS Variables
Start by defining semantic color variables in :root.
These represent intent, not specific colors.
:root {
--color-bg: #ffffff;
--color-text: #111111;
--color-muted: #666666;
--color-border: #e5e5e5;
--color-accent: #0066cc;
}
Use these variables throughout your theme:
body {
background-color: var(--color-bg);
color: var(--color-text);
}
a {
color: var(--color-accent);
}
hr {
border-color: var(--color-border);
}
2) Add Dark Mode via prefers-color-scheme
Override only the variables inside the dark mode media query.
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0f0f0f;
--color-text: #f1f1f1;
--color-muted: #9a9a9a;
--color-border: #2a2a2a;
--color-accent: #4da3ff;
}
}
This automatically enables dark mode for users who prefer it—no JavaScript required.
3) Scope Dark Mode to the Front End Only
You usually do not want to affect the WordPress admin UI.
Keep your CSS in front-end styles only.
If your theme outputs shared CSS for admin and front end, scope it:
body:not(.wp-admin) {
background-color: var(--color-bg);
}
4) Optional: Add a Manual Dark Mode Toggle (No Plugin)
Some users want to override system preferences.
You can do this with a single class and localStorage.
CSS: Class-Based Override
html.is-dark {
--color-bg: #0f0f0f;
--color-text: #f1f1f1;
--color-muted: #9a9a9a;
--color-border: #2a2a2a;
--color-accent: #4da3ff;
}
html.is-light {
--color-bg: #ffffff;
--color-text: #111111;
--color-muted: #666666;
--color-border: #e5e5e5;
--color-accent: #0066cc;
}
Class-based overrides should come after the media query in your CSS.
JavaScript: Apply Saved Preference Early
This script runs before rendering to avoid a flash of the wrong theme.
(function () {
try {
var mode = localStorage.getItem('color-scheme');
if (mode === 'dark') {
document.documentElement.classList.add('is-dark');
} else if (mode === 'light') {
document.documentElement.classList.add('is-light');
}
} catch (e) {}
})();
Toggle Button Logic
function toggleColorScheme() {
var root = document.documentElement;
var isDark = root.classList.toggle('is-dark');
root.classList.remove(isDark ? 'is-light' : 'is-dark');
root.classList.add(isDark ? 'is-dark' : 'is-light');
localStorage.setItem('color-scheme', isDark ? 'dark' : 'light');
}
Attach this to a button in your header or footer.
5) Enqueue the Script Safely in WordPress
Inline scripts that affect rendering should run early.
Use wp_add_inline_script().
<?php
add_action( 'wp_enqueue_scripts', function () {
if ( is_admin() ) {
return;
}
wp_enqueue_script(
'theme-main',
get_template_directory_uri() . '/assets/js/main.js',
array(),
'1.0.0',
true
);
$inline = <<<JS
(function () {
try {
var mode = localStorage.getItem('color-scheme');
if (mode === 'dark') {
document.documentElement.classList.add('is-dark');
} else if (mode === 'light') {
document.documentElement.classList.add('is-light');
}
} catch (e) {}
})();
JS;
wp_add_inline_script( 'theme-main', $inline, 'before' );
}, 20 );
6) Images and Media in Dark Mode
Avoid inverting images globally.
Instead:
- Use transparent PNG/SVG where possible
- Provide alternate assets for dark backgrounds if needed
- Avoid
filter: invert()on large sections
For icons, SVG with currentColor works best.
7) Accessibility Considerations
- Maintain sufficient contrast ratios in both modes
- Do not rely on color alone to convey meaning
- Ensure focus styles are visible in dark mode
Common Mistakes
- Duplicating entire stylesheets for dark mode
- Hardcoding colors instead of using variables
- Applying dark mode styles to wp-admin unintentionally
- Triggering layout shifts when toggling modes
Summary
- Use CSS variables as the foundation of dark mode
- Enable automatic dark mode with
prefers-color-scheme - Add a class-based override only if you need a toggle
- Store user preference in
localStorage - Keep the implementation lightweight and front-end only
With this approach, you get modern dark mode support in WordPress
without plugins, without bloat, and without sacrificing maintainability.
🎨 Want to learn more? Visit our WordPress Customization Hub for tips and advanced techniques.