I’ll build this as a full advanced dark-mode.js for your RX Theme: toggle support, system preference, localStorage, accessibility, keyboard shortcut, smooth transition, admin-safe behavior, and custom events for future expansion.
My dear friend, create this file:
rx-theme/inc/js/dark-mode.js
Then paste this full advanced code inside it.
/*!
* RX Theme Dark Mode System
* File: inc/js/dark-mode.js
* Theme: RX Theme
* Author: RxHarun
*
* Features:
* - Light / Dark / System mode
* - localStorage save
* - System color-scheme detection
* - Accessible buttons
* - Keyboard shortcut
* - Smooth transition
* - Multiple toggle button support
* - Works with body/html classes and data attributes
* - Dispatches custom events for future theme modules
*/
(function () {
'use strict';
/**
* Main RX Dark Mode Object
*/
const RXDarkMode = {
storageKey: 'rx_theme_color_mode',
defaultMode: 'system',
html: document.documentElement,
body: null,
selectors: {
toggle: '[data-rx-dark-toggle], .rx-dark-mode-toggle',
lightButton: '[data-rx-theme-light]',
darkButton: '[data-rx-theme-dark]',
systemButton: '[data-rx-theme-system]',
modeText: '[data-rx-theme-mode-text]',
icon: '[data-rx-theme-icon]',
},
classes: {
dark: 'rx-dark-mode',
light: 'rx-light-mode',
system: 'rx-system-mode',
transition: 'rx-theme-transition',
active: 'is-active',
},
icons: {
light: '☀️',
dark: '🌙',
system: '🖥️',
},
labels: {
light: 'Light mode',
dark: 'Dark mode',
system: 'System mode',
},
/**
* Initialize dark mode system
*/
init() {
this.body = document.body;
if (!this.html) {
return;
}
this.applySavedMode();
this.bindEvents();
this.watchSystemChange();
this.setInitialMetaThemeColor();
this.preventFlash();
this.dispatch('rxDarkModeReady', {
mode: this.getSavedMode(),
activeMode: this.getActiveMode(),
});
},
/**
* Get saved mode from localStorage
*/
getSavedMode() {
try {
return localStorage.getItem(this.storageKey) || this.defaultMode;
} catch (error) {
return this.defaultMode;
}
},
/**
* Save selected mode
*/
saveMode(mode) {
try {
localStorage.setItem(this.storageKey, mode);
} catch (error) {
// localStorage may be blocked.
}
},
/**
* Remove saved mode
*/
clearSavedMode() {
try {
localStorage.removeItem(this.storageKey);
} catch (error) {
// localStorage may be blocked.
}
},
/**
* Check system dark preference
*/
systemPrefersDark() {
return (
window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches
);
},
/**
* Check reduced motion preference
*/
prefersReducedMotion() {
return (
window.matchMedia &&
window.matchMedia('(prefers-reduced-motion: reduce)').matches
);
},
/**
* Get actual active mode
*/
getActiveMode() {
const savedMode = this.getSavedMode();
if (savedMode === 'system') {
return this.systemPrefersDark() ? 'dark' : 'light';
}
return savedMode === 'dark' ? 'dark' : 'light';
},
/**
* Apply saved mode on page load
*/
applySavedMode() {
const savedMode = this.getSavedMode();
this.setMode(savedMode, false);
},
/**
* Main mode setter
*
* @param {string} mode light | dark | system
* @param {boolean} save Whether to save mode
*/
setMode(mode, save = true) {
if (!['light', 'dark', 'system'].includes(mode)) {
mode = this.defaultMode;
}
const activeMode =
mode === 'system'
? this.systemPrefersDark()
? 'dark'
: 'light'
: mode;
this.enableTransition();
this.html.classList.remove(
this.classes.dark,
this.classes.light,
this.classes.system
);
if (this.body) {
this.body.classList.remove(
this.classes.dark,
this.classes.light,
this.classes.system
);
}
this.html.classList.add(
activeMode === 'dark' ? this.classes.dark : this.classes.light
);
if (this.body) {
this.body.classList.add(
activeMode === 'dark' ? this.classes.dark : this.classes.light
);
}
if (mode === 'system') {
this.html.classList.add(this.classes.system);
if (this.body) {
this.body.classList.add(this.classes.system);
}
}
this.html.setAttribute('data-theme', activeMode);
this.html.setAttribute('data-rx-theme', activeMode);
this.html.setAttribute('data-rx-theme-mode', mode);
this.html.style.colorScheme = activeMode;
if (this.body) {
this.body.setAttribute('data-theme', activeMode);
this.body.setAttribute('data-rx-theme', activeMode);
this.body.setAttribute('data-rx-theme-mode', mode);
}
if (save) {
this.saveMode(mode);
}
this.updateButtons(mode, activeMode);
this.updateMetaThemeColor(activeMode);
this.dispatch('rxDarkModeChange', {
selectedMode: mode,
activeMode: activeMode,
systemDark: this.systemPrefersDark(),
});
},
/**
* Toggle between dark and light
*/
toggleMode() {
const currentMode = this.getSavedMode();
const activeMode = this.getActiveMode();
if (currentMode === 'system') {
this.setMode(activeMode === 'dark' ? 'light' : 'dark');
return;
}
this.setMode(currentMode === 'dark' ? 'light' : 'dark');
},
/**
* Cycle light -> dark -> system
*/
cycleMode() {
const currentMode = this.getSavedMode();
if (currentMode === 'light') {
this.setMode('dark');
} else if (currentMode === 'dark') {
this.setMode('system');
} else {
this.setMode('light');
}
},
/**
* Bind button, keyboard, and page events
*/
bindEvents() {
document.addEventListener('click', (event) => {
const toggleButton = event.target.closest(this.selectors.toggle);
const lightButton = event.target.closest(this.selectors.lightButton);
const darkButton = event.target.closest(this.selectors.darkButton);
const systemButton = event.target.closest(this.selectors.systemButton);
if (toggleButton) {
event.preventDefault();
const action = toggleButton.getAttribute('data-rx-dark-toggle');
if (action === 'cycle') {
this.cycleMode();
} else {
this.toggleMode();
}
return;
}
if (lightButton) {
event.preventDefault();
this.setMode('light');
return;
}
if (darkButton) {
event.preventDefault();
this.setMode('dark');
return;
}
if (systemButton) {
event.preventDefault();
this.setMode('system');
}
});
document.addEventListener('keydown', (event) => {
this.handleKeyboardShortcut(event);
});
window.addEventListener('storage', (event) => {
if (event.key === this.storageKey) {
this.applySavedMode();
}
});
},
/**
* Keyboard shortcut:
* Ctrl + Alt + D = toggle dark mode
*/
handleKeyboardShortcut(event) {
const isInput =
event.target &&
['INPUT', 'TEXTAREA', 'SELECT'].includes(event.target.tagName);
if (isInput || event.target.isContentEditable) {
return;
}
if (event.ctrlKey && event.altKey && event.key.toLowerCase() === 'd') {
event.preventDefault();
this.toggleMode();
}
},
/**
* Watch OS/browser system color scheme change
*/
watchSystemChange() {
if (!window.matchMedia) {
return;
}
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = () => {
if (this.getSavedMode() === 'system') {
this.setMode('system', false);
}
};
if (typeof mediaQuery.addEventListener === 'function') {
mediaQuery.addEventListener('change', handleChange);
} else if (typeof mediaQuery.addListener === 'function') {
mediaQuery.addListener(handleChange);
}
},
/**
* Update all buttons and labels
*/
updateButtons(selectedMode, activeMode) {
const toggles = document.querySelectorAll(this.selectors.toggle);
const lightButtons = document.querySelectorAll(this.selectors.lightButton);
const darkButtons = document.querySelectorAll(this.selectors.darkButton);
const systemButtons = document.querySelectorAll(this.selectors.systemButton);
const modeTexts = document.querySelectorAll(this.selectors.modeText);
const icons = document.querySelectorAll(this.selectors.icon);
toggles.forEach((button) => {
button.setAttribute('aria-pressed', activeMode === 'dark' ? 'true' : 'false');
button.setAttribute(
'aria-label',
activeMode === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'
);
button.setAttribute('title', 'Toggle dark mode');
if (!button.hasAttribute('type') && button.tagName === 'BUTTON') {
button.setAttribute('type', 'button');
}
});
this.setActiveState(lightButtons, selectedMode === 'light');
this.setActiveState(darkButtons, selectedMode === 'dark');
this.setActiveState(systemButtons, selectedMode === 'system');
modeTexts.forEach((text) => {
text.textContent = this.labels[selectedMode] || this.labels.system;
});
icons.forEach((icon) => {
icon.textContent = this.icons[selectedMode] || this.icons.system;
});
},
/**
* Add or remove active state
*/
setActiveState(elements, active) {
elements.forEach((element) => {
element.classList.toggle(this.classes.active, active);
element.setAttribute('aria-pressed', active ? 'true' : 'false');
});
},
/**
* Add smooth transition class briefly
*/
enableTransition() {
if (this.prefersReducedMotion()) {
return;
}
this.html.classList.add(this.classes.transition);
if (this.body) {
this.body.classList.add(this.classes.transition);
}
window.clearTimeout(this.transitionTimer);
this.transitionTimer = window.setTimeout(() => {
this.html.classList.remove(this.classes.transition);
if (this.body) {
this.body.classList.remove(this.classes.transition);
}
}, 350);
},
/**
* Prevent flash helper
*/
preventFlash() {
this.html.classList.add('rx-dark-mode-loaded');
if (this.body) {
this.body.classList.add('rx-dark-mode-loaded');
}
},
/**
* Add or update browser mobile address bar color
*/
setInitialMetaThemeColor() {
if (!document.querySelector('meta[name="theme-color"]')) {
const meta = document.createElement('meta');
meta.name = 'theme-color';
document.head.appendChild(meta);
}
this.updateMetaThemeColor(this.getActiveMode());
},
/**
* Update browser UI color
*/
updateMetaThemeColor(activeMode) {
const metaThemeColor = document.querySelector('meta[name="theme-color"]');
if (!metaThemeColor) {
return;
}
const lightColor =
getComputedStyle(this.html)
.getPropertyValue('--rx-light-theme-color')
.trim() || '#ffffff';
const darkColor =
getComputedStyle(this.html)
.getPropertyValue('--rx-dark-theme-color')
.trim() || '#0f172a';
metaThemeColor.setAttribute(
'content',
activeMode === 'dark' ? darkColor : lightColor
);
},
/**
* Dispatch custom event for future RX Theme features
*/
dispatch(eventName, detail = {}) {
document.dispatchEvent(
new CustomEvent(eventName, {
detail,
bubbles: true,
})
);
},
};
/**
* Load as early as possible
*/
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () {
RXDarkMode.init();
});
} else {
RXDarkMode.init();
}
/**
* Make public for future use
*
* Example:
* window.RXDarkMode.setMode('dark');
* window.RXDarkMode.setMode('light');
* window.RXDarkMode.setMode('system');
*/
window.RXDarkMode = RXDarkMode;
})();
Add this CSS also in your theme style.css or dark-mode CSS file:
:root {
--rx-light-theme-color: #ffffff;
--rx-dark-theme-color: #0f172a;
--rx-light-bg: #ffffff;
--rx-light-text: #111827;
--rx-dark-bg: #0f172a;
--rx-dark-text: #e5e7eb;
}
html.rx-light-mode,
body.rx-light-mode {
background-color: var(--rx-light-bg);
color: var(--rx-light-text);
}
html.rx-dark-mode,
body.rx-dark-mode {
background-color: var(--rx-dark-bg);
color: var(--rx-dark-text);
}
html.rx-theme-transition,
html.rx-theme-transition *,
body.rx-theme-transition,
body.rx-theme-transition * {
transition:
background-color 0.25s ease,
color 0.25s ease,
border-color 0.25s ease,
box-shadow 0.25s ease,
fill 0.25s ease,
stroke 0.25s ease;
}
.rx-dark-mode a {
color: #93c5fd;
}
.rx-dark-mode input,
.rx-dark-mode textarea,
.rx-dark-mode select {
background-color: #111827;
color: #f9fafb;
border-color: #374151;
}
.rx-dark-mode button,
.rx-dark-mode .button,
.rx-dark-mode .wp-block-button__link {
background-color: #1f2937;
color: #f9fafb;
border-color: #374151;
}
.rx-dark-mode .site-header,
.rx-dark-mode .site-footer,
.rx-dark-mode .rx-card,
.rx-dark-mode article,
.rx-dark-mode .widget {
background-color: #111827;
color: #e5e7eb;
border-color: #374151;
}
.rx-dark-mode img {
opacity: 0.95;
}
.rx-dark-mode-toggle,
[data-rx-dark-toggle],
[data-rx-theme-light],
[data-rx-theme-dark],
[data-rx-theme-system] {
cursor: pointer;
}
.is-active {
font-weight: 700;
}
Example HTML button:
<button class="rx-dark-mode-toggle" data-rx-dark-toggle type="button">
<span data-rx-theme-icon>🌙</span>
<span data-rx-theme-mode-text>System mode</span>
</button>
Advanced 3-button mode switch:
<div class="rx-theme-switcher" role="group" aria-label="Theme switcher">
<button data-rx-theme-light type="button">Light</button>
<button data-rx-theme-dark type="button">Dark</button>
<button data-rx-theme-system type="button">System</button>
</div>
Then enqueue it from functions.php:
function rx_theme_enqueue_dark_mode_script() {
wp_enqueue_script(
'rx-dark-mode',
get_template_directory_uri() . '/inc/js/dark-mode.js',
array(),
wp_get_theme()->get( 'Version' ),
true
);
}
add_action( 'wp_enqueue_scripts', 'rx_theme_enqueue_dark_mode_script' );
For best result, add the button in your header.php near the menu or top bar.