dark-mode.js

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.

Leave a Reply

Your email address will not be published. Required fields are marked *