chunk-005-dark-mode.js

I’ll build this as a standalone advanced dark-mode chunk for RX Theme, designed to work safely even before the rest of your theme is finished. It will include system preference detection, localStorage, buttons/toggles, keyboard support, meta theme-color updates, CSS variable hooks, accessibility, and WordPress-friendly events.

Yes my dear friend — create this file:

assets/static-js/static-chunks-js/chunk-005-dark-mode.js

Paste this full advanced code inside it.

/*!
 * RX Theme - Advanced Dark Mode Chunk
 * File: assets/static-js/static-chunks-js/chunk-005-dark-mode.js
 *
 * Purpose:
 * - Advanced dark / light / system theme controller
 * - WordPress theme friendly
 * - LocalStorage support
 * - System preference support
 * - Accessible buttons and toggles
 * - Meta theme-color update
 * - CSS variable friendly
 * - Custom events for future RX Theme modules
 *
 * Required CSS idea:
 * html[data-rx-theme="dark"] { color-scheme: dark; }
 * html[data-rx-theme="light"] { color-scheme: light; }
 */

(function RXDarkModeChunk(window, document) {
  'use strict';

  if (!window || !document) return;

  /**
   * Prevent duplicate loading.
   */
  if (window.RX_DARK_MODE_CHUNK_LOADED) {
    return;
  }

  window.RX_DARK_MODE_CHUNK_LOADED = true;

  /**
   * Main namespace.
   */
  var RX = window.RXTheme || {};
  window.RXTheme = RX;

  /**
   * Configuration.
   */
  var CONFIG = {
    storageKey: 'rx-theme-color-mode',
    previousStorageKey: 'rx-theme-previous-color-mode',

    defaultMode: 'system',

    allowedModes: ['light', 'dark', 'system'],

    htmlAttribute: 'data-rx-theme',
    htmlModeAttribute: 'data-rx-theme-mode',
    htmlResolvedAttribute: 'data-rx-theme-resolved',
    bodyClassPrefix: 'rx-theme-',

    toggleSelector: '[data-rx-dark-toggle]',
    buttonSelector: '[data-rx-theme-button]',
    selectSelector: '[data-rx-theme-select]',
    checkboxSelector: '[data-rx-theme-checkbox]',
    iconSelector: '[data-rx-theme-icon]',
    labelSelector: '[data-rx-theme-label]',
    statusSelector: '[data-rx-theme-status]',

    darkClass: 'is-dark-mode',
    lightClass: 'is-light-mode',
    systemClass: 'is-system-mode',

    activeClass: 'is-active',
    loadingClass: 'rx-theme-changing',

    transitionClass: 'rx-theme-transition',
    noTransitionClass: 'rx-theme-no-transition',

    metaThemeColorLight: '#ffffff',
    metaThemeColorDark: '#0b0f19',

    eventPrefix: 'rx:theme:',

    transitionDuration: 250,

    enableKeyboardShortcut: true,
    keyboardShortcutKey: 'd',
    keyboardShortcutAlt: true,
    keyboardShortcutShift: true,

    enableMetaThemeColor: true,
    enableColorScheme: true,
    enableViewTransition: false,

    debug: false
  };

  /**
   * State.
   */
  var state = {
    initialized: false,
    currentMode: CONFIG.defaultMode,
    resolvedMode: 'light',
    previousMode: null,
    mediaQuery: null,
    observers: [],
    callbacks: {
      change: [],
      beforeChange: [],
      afterChange: []
    }
  };

  /**
   * Helpers.
   */
  function log() {
    if (!CONFIG.debug || !window.console) return;
    try {
      window.console.log.apply(window.console, ['[RX Dark Mode]'].concat(Array.prototype.slice.call(arguments)));
    } catch (error) {}
  }

  function warn() {
    if (!window.console) return;
    try {
      window.console.warn.apply(window.console, ['[RX Dark Mode]'].concat(Array.prototype.slice.call(arguments)));
    } catch (error) {}
  }

  function isString(value) {
    return typeof value === 'string';
  }

  function normalizeMode(mode) {
    if (!isString(mode)) return CONFIG.defaultMode;

    mode = mode.toLowerCase().trim();

    if (CONFIG.allowedModes.indexOf(mode) !== -1) {
      return mode;
    }

    if (mode === 'auto') return 'system';
    if (mode === 'night') return 'dark';
    if (mode === 'day') return 'light';

    return CONFIG.defaultMode;
  }

  function isAllowedMode(mode) {
    return CONFIG.allowedModes.indexOf(mode) !== -1;
  }

  function safeLocalStorageGet(key) {
    try {
      return window.localStorage.getItem(key);
    } catch (error) {
      return null;
    }
  }

  function safeLocalStorageSet(key, value) {
    try {
      window.localStorage.setItem(key, value);
      return true;
    } catch (error) {
      return false;
    }
  }

  function safeLocalStorageRemove(key) {
    try {
      window.localStorage.removeItem(key);
      return true;
    } catch (error) {
      return false;
    }
  }

  function getDocumentElement() {
    return document.documentElement;
  }

  function getBody() {
    return document.body;
  }

  function getSystemPreference() {
    if (!window.matchMedia) {
      return 'light';
    }

    try {
      return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
    } catch (error) {
      return 'light';
    }
  }

  function getMediaQuery() {
    if (!window.matchMedia) return null;

    if (!state.mediaQuery) {
      try {
        state.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
      } catch (error) {
        state.mediaQuery = null;
      }
    }

    return state.mediaQuery;
  }

  function resolveMode(mode) {
    mode = normalizeMode(mode);

    if (mode === 'system') {
      return getSystemPreference();
    }

    return mode;
  }

  function getStoredMode() {
    var stored = safeLocalStorageGet(CONFIG.storageKey);
    return normalizeMode(stored || CONFIG.defaultMode);
  }

  function setStoredMode(mode) {
    mode = normalizeMode(mode);

    if (mode === CONFIG.defaultMode) {
      safeLocalStorageSet(CONFIG.storageKey, mode);
      return;
    }

    safeLocalStorageSet(CONFIG.storageKey, mode);
  }

  function getInitialMode() {
    var html = getDocumentElement();

    var attrMode =
      html.getAttribute(CONFIG.htmlModeAttribute) ||
      html.getAttribute(CONFIG.htmlAttribute);

    if (attrMode && isAllowedMode(normalizeMode(attrMode))) {
      return normalizeMode(attrMode);
    }

    return getStoredMode();
  }

  function createEvent(name, detail) {
    var eventName = CONFIG.eventPrefix + name;

    try {
      return new CustomEvent(eventName, {
        bubbles: true,
        cancelable: false,
        detail: detail || {}
      });
    } catch (error) {
      var event = document.createEvent('CustomEvent');
      event.initCustomEvent(eventName, true, false, detail || {});
      return event;
    }
  }

  function dispatchThemeEvent(name, detail) {
    var event = createEvent(name, detail);
    document.dispatchEvent(event);
    return event;
  }

  function runCallbacks(type, payload) {
    var list = state.callbacks[type];

    if (!list || !list.length) return;

    list.forEach(function runCallback(callback) {
      try {
        callback(payload);
      } catch (error) {
        warn('Callback error:', error);
      }
    });
  }

  function getMetaThemeColor() {
    var meta = document.querySelector('meta[name="theme-color"]');

    if (!meta) {
      meta = document.createElement('meta');
      meta.setAttribute('name', 'theme-color');

      if (document.head) {
        document.head.appendChild(meta);
      }
    }

    return meta;
  }

  function updateMetaThemeColor(resolvedMode) {
    if (!CONFIG.enableMetaThemeColor) return;

    var meta = getMetaThemeColor();
    if (!meta) return;

    var color = resolvedMode === 'dark'
      ? CONFIG.metaThemeColorDark
      : CONFIG.metaThemeColorLight;

    meta.setAttribute('content', color);
  }

  function updateColorScheme(resolvedMode) {
    if (!CONFIG.enableColorScheme) return;

    var html = getDocumentElement();

    try {
      html.style.colorScheme = resolvedMode === 'dark' ? 'dark' : 'light';
    } catch (error) {}
  }

  function removeBodyThemeClasses() {
    var body = getBody();
    if (!body) return;

    body.classList.remove(CONFIG.darkClass);
    body.classList.remove(CONFIG.lightClass);
    body.classList.remove(CONFIG.systemClass);

    CONFIG.allowedModes.forEach(function removeModeClass(mode) {
      body.classList.remove(CONFIG.bodyClassPrefix + mode);
    });
  }

  function applyBodyClasses(mode, resolvedMode) {
    var body = getBody();
    if (!body) return;

    removeBodyThemeClasses();

    body.classList.add(CONFIG.bodyClassPrefix + mode);

    if (resolvedMode === 'dark') {
      body.classList.add(CONFIG.darkClass);
    } else {
      body.classList.add(CONFIG.lightClass);
    }

    if (mode === 'system') {
      body.classList.add(CONFIG.systemClass);
    }
  }

  function applyHtmlAttributes(mode, resolvedMode) {
    var html = getDocumentElement();

    html.setAttribute(CONFIG.htmlModeAttribute, mode);
    html.setAttribute(CONFIG.htmlResolvedAttribute, resolvedMode);

    /**
     * Important:
     * data-rx-theme should hold the resolved visual mode.
     * Example:
     * selected mode = system
     * system says dark
     * data-rx-theme = dark
     */
    html.setAttribute(CONFIG.htmlAttribute, resolvedMode);
  }

  function setPressedState(element, active) {
    if (!element) return;

    if (
      element.tagName === 'BUTTON' ||
      element.getAttribute('role') === 'button' ||
      element.hasAttribute('aria-pressed')
    ) {
      element.setAttribute('aria-pressed', active ? 'true' : 'false');
    }
  }

  function setCheckedState(element, checked) {
    if (!element) return;

    if (element.type === 'checkbox') {
      element.checked = !!checked;
    }

    element.setAttribute('aria-checked', checked ? 'true' : 'false');
  }

  function getReadableModeLabel(mode, resolvedMode) {
    if (mode === 'system') {
      return resolvedMode === 'dark'
        ? 'System dark mode'
        : 'System light mode';
    }

    if (mode === 'dark') return 'Dark mode';
    return 'Light mode';
  }

  function getNextMode(mode) {
    mode = normalizeMode(mode);

    if (mode === 'light') return 'dark';
    if (mode === 'dark') return 'system';
    return 'light';
  }

  function getOppositeResolvedMode() {
    return state.resolvedMode === 'dark' ? 'light' : 'dark';
  }

  function updateIcons(mode, resolvedMode) {
    var icons = document.querySelectorAll(CONFIG.iconSelector);

    if (!icons || !icons.length) return;

    icons.forEach(function updateIcon(icon) {
      var iconMode = icon.getAttribute('data-rx-theme-icon');

      icon.classList.remove(CONFIG.activeClass);
      icon.setAttribute('aria-hidden', 'true');

      if (iconMode === mode || iconMode === resolvedMode) {
        icon.classList.add(CONFIG.activeClass);
      }

      if (iconMode === 'dark' && resolvedMode === 'dark') {
        icon.classList.add(CONFIG.activeClass);
      }

      if (iconMode === 'light' && resolvedMode === 'light') {
        icon.classList.add(CONFIG.activeClass);
      }

      if (iconMode === 'system' && mode === 'system') {
        icon.classList.add(CONFIG.activeClass);
      }
    });
  }

  function updateLabels(mode, resolvedMode) {
    var labels = document.querySelectorAll(CONFIG.labelSelector);
    var text = getReadableModeLabel(mode, resolvedMode);

    if (!labels || !labels.length) return;

    labels.forEach(function updateLabel(label) {
      var labelType = label.getAttribute('data-rx-theme-label');

      if (!labelType || labelType === 'current') {
        label.textContent = text;
      }

      if (labelType === 'next') {
        label.textContent = getReadableModeLabel(getNextMode(mode), resolveMode(getNextMode(mode)));
      }

      if (labelType === 'resolved') {
        label.textContent = resolvedMode === 'dark' ? 'Dark' : 'Light';
      }

      if (labelType === 'selected') {
        label.textContent = mode.charAt(0).toUpperCase() + mode.slice(1);
      }
    });
  }

  function updateStatus(mode, resolvedMode) {
    var statuses = document.querySelectorAll(CONFIG.statusSelector);
    var text = getReadableModeLabel(mode, resolvedMode) + ' enabled';

    if (!statuses || !statuses.length) return;

    statuses.forEach(function updateStatusText(status) {
      status.textContent = text;
    });
  }

  function updateButtons(mode, resolvedMode) {
    var buttons = document.querySelectorAll(CONFIG.buttonSelector);

    if (!buttons || !buttons.length) return;

    buttons.forEach(function updateButton(button) {
      var buttonMode = normalizeMode(button.getAttribute('data-rx-theme-button'));

      var active = buttonMode === mode;

      button.classList.toggle(CONFIG.activeClass, active);
      setPressedState(button, active);

      button.setAttribute('data-rx-active', active ? 'true' : 'false');
      button.setAttribute('data-rx-resolved', resolvedMode);

      if (!button.getAttribute('aria-label')) {
        button.setAttribute('aria-label', 'Set ' + buttonMode + ' mode');
      }
    });
  }

  function updateToggles(mode, resolvedMode) {
    var toggles = document.querySelectorAll(CONFIG.toggleSelector);

    if (!toggles || !toggles.length) return;

    toggles.forEach(function updateToggle(toggle) {
      var isDark = resolvedMode === 'dark';

      toggle.classList.toggle(CONFIG.activeClass, isDark);
      toggle.setAttribute('data-rx-active', isDark ? 'true' : 'false');
      toggle.setAttribute('data-rx-mode', mode);
      toggle.setAttribute('data-rx-resolved', resolvedMode);

      setPressedState(toggle, isDark);

      if (!toggle.getAttribute('aria-label')) {
        toggle.setAttribute('aria-label', 'Toggle dark mode');
      }

      toggle.setAttribute(
        'title',
        isDark ? 'Switch theme mode' : 'Switch theme mode'
      );
    });
  }

  function updateCheckboxes(mode, resolvedMode) {
    var checkboxes = document.querySelectorAll(CONFIG.checkboxSelector);

    if (!checkboxes || !checkboxes.length) return;

    checkboxes.forEach(function updateCheckbox(checkbox) {
      var isDark = resolvedMode === 'dark';

      setCheckedState(checkbox, isDark);

      checkbox.setAttribute('data-rx-mode', mode);
      checkbox.setAttribute('data-rx-resolved', resolvedMode);
    });
  }

  function updateSelects(mode) {
    var selects = document.querySelectorAll(CONFIG.selectSelector);

    if (!selects || !selects.length) return;

    selects.forEach(function updateSelect(select) {
      select.value = mode;
    });
  }

  function updateControls(mode, resolvedMode) {
    updateButtons(mode, resolvedMode);
    updateToggles(mode, resolvedMode);
    updateCheckboxes(mode, resolvedMode);
    updateSelects(mode);
    updateIcons(mode, resolvedMode);
    updateLabels(mode, resolvedMode);
    updateStatus(mode, resolvedMode);
  }

  function disableTransitionsTemporarily() {
    var html = getDocumentElement();

    html.classList.add(CONFIG.noTransitionClass);

    window.setTimeout(function removeNoTransition() {
      html.classList.remove(CONFIG.noTransitionClass);
    }, 50);
  }

  function addTransitionClass() {
    var html = getDocumentElement();

    html.classList.add(CONFIG.transitionClass);
    html.classList.add(CONFIG.loadingClass);

    window.setTimeout(function removeTransitionClass() {
      html.classList.remove(CONFIG.loadingClass);
    }, CONFIG.transitionDuration);
  }

  function applyTheme(mode, options) {
    options = options || {};

    mode = normalizeMode(mode);

    var previousMode = state.currentMode;
    var previousResolvedMode = state.resolvedMode;
    var resolvedMode = resolveMode(mode);

    var payload = {
      mode: mode,
      resolvedMode: resolvedMode,
      previousMode: previousMode,
      previousResolvedMode: previousResolvedMode,
      source: options.source || 'api',
      timestamp: Date.now()
    };

    runCallbacks('beforeChange', payload);
    dispatchThemeEvent('before-change', payload);

    if (options.disableTransition) {
      disableTransitionsTemporarily();
    } else {
      addTransitionClass();
    }

    applyHtmlAttributes(mode, resolvedMode);
    applyBodyClasses(mode, resolvedMode);
    updateMetaThemeColor(resolvedMode);
    updateColorScheme(resolvedMode);
    updateControls(mode, resolvedMode);

    state.previousMode = previousMode;
    state.currentMode = mode;
    state.resolvedMode = resolvedMode;

    if (options.save !== false) {
      setStoredMode(mode);
      safeLocalStorageSet(CONFIG.previousStorageKey, previousMode);
    }

    runCallbacks('change', payload);
    dispatchThemeEvent('change', payload);

    window.setTimeout(function afterChange() {
      runCallbacks('afterChange', payload);
      dispatchThemeEvent('after-change', payload);
    }, CONFIG.transitionDuration);

    log('Theme applied:', payload);

    return payload;
  }

  function setMode(mode, options) {
    return applyTheme(mode, options || {});
  }

  function toggleMode(options) {
    options = options || {};

    var nextMode;

    /**
     * Simple toggle:
     * dark -> light
     * light -> dark
     * system -> opposite of resolved mode
     */
    if (options.cycle === true) {
      nextMode = getNextMode(state.currentMode);
    } else {
      nextMode = getOppositeResolvedMode();
    }

    return setMode(nextMode, {
      source: options.source || 'toggle'
    });
  }

  function cycleMode(options) {
    return setMode(getNextMode(state.currentMode), {
      source: options && options.source ? options.source : 'cycle'
    });
  }

  function resetMode(options) {
    safeLocalStorageRemove(CONFIG.storageKey);

    return setMode(CONFIG.defaultMode, {
      source: options && options.source ? options.source : 'reset',
      save: true
    });
  }

  function isDark() {
    return state.resolvedMode === 'dark';
  }

  function isLight() {
    return state.resolvedMode === 'light';
  }

  function isSystem() {
    return state.currentMode === 'system';
  }

  function on(type, callback) {
    if (!state.callbacks[type]) {
      state.callbacks[type] = [];
    }

    if (typeof callback === 'function') {
      state.callbacks[type].push(callback);
    }

    return function unsubscribe() {
      off(type, callback);
    };
  }

  function off(type, callback) {
    if (!state.callbacks[type]) return;

    state.callbacks[type] = state.callbacks[type].filter(function removeCallback(item) {
      return item !== callback;
    });
  }

  /**
   * Event handlers.
   */
  function handleToggleClick(event) {
    var target = event.target.closest(CONFIG.toggleSelector);

    if (!target) return;

    event.preventDefault();

    var cycle = target.getAttribute('data-rx-cycle') === 'true';

    if (cycle) {
      cycleMode({ source: 'toggle-click' });
    } else {
      toggleMode({ source: 'toggle-click' });
    }
  }

  function handleButtonClick(event) {
    var target = event.target.closest(CONFIG.buttonSelector);

    if (!target) return;

    event.preventDefault();

    var mode = normalizeMode(target.getAttribute('data-rx-theme-button'));

    setMode(mode, {
      source: 'button-click'
    });
  }

  function handleSelectChange(event) {
    var target = event.target;

    if (!target || !target.matches(CONFIG.selectSelector)) return;

    var mode = normalizeMode(target.value);

    setMode(mode, {
      source: 'select-change'
    });
  }

  function handleCheckboxChange(event) {
    var target = event.target;

    if (!target || !target.matches(CONFIG.checkboxSelector)) return;

    var mode = target.checked ? 'dark' : 'light';

    setMode(mode, {
      source: 'checkbox-change'
    });
  }

  function handleKeyboardShortcut(event) {
    if (!CONFIG.enableKeyboardShortcut) return;

    var tagName = event.target && event.target.tagName
      ? event.target.tagName.toLowerCase()
      : '';

    var isTyping =
      tagName === 'input' ||
      tagName === 'textarea' ||
      tagName === 'select' ||
      event.target.isContentEditable;

    if (isTyping) return;

    var keyMatches = String(event.key || '').toLowerCase() === CONFIG.keyboardShortcutKey;
    var altMatches = CONFIG.keyboardShortcutAlt ? event.altKey : true;
    var shiftMatches = CONFIG.keyboardShortcutShift ? event.shiftKey : true;

    if (keyMatches && altMatches && shiftMatches) {
      event.preventDefault();
      cycleMode({ source: 'keyboard-shortcut' });
    }
  }

  function handleSystemPreferenceChange() {
    if (state.currentMode !== 'system') return;

    applyTheme('system', {
      source: 'system-preference',
      save: false
    });
  }

  function bindSystemPreferenceListener() {
    var mediaQuery = getMediaQuery();

    if (!mediaQuery) return;

    if (typeof mediaQuery.addEventListener === 'function') {
      mediaQuery.addEventListener('change', handleSystemPreferenceChange);
    } else if (typeof mediaQuery.addListener === 'function') {
      mediaQuery.addListener(handleSystemPreferenceChange);
    }
  }

  function unbindSystemPreferenceListener() {
    var mediaQuery = getMediaQuery();

    if (!mediaQuery) return;

    if (typeof mediaQuery.removeEventListener === 'function') {
      mediaQuery.removeEventListener('change', handleSystemPreferenceChange);
    } else if (typeof mediaQuery.removeListener === 'function') {
      mediaQuery.removeListener(handleSystemPreferenceChange);
    }
  }

  function bindEvents() {
    document.addEventListener('click', handleToggleClick, false);
    document.addEventListener('click', handleButtonClick, false);
    document.addEventListener('change', handleSelectChange, false);
    document.addEventListener('change', handleCheckboxChange, false);
    document.addEventListener('keydown', handleKeyboardShortcut, false);

    bindSystemPreferenceListener();
  }

  function unbindEvents() {
    document.removeEventListener('click', handleToggleClick, false);
    document.removeEventListener('click', handleButtonClick, false);
    document.removeEventListener('change', handleSelectChange, false);
    document.removeEventListener('change', handleCheckboxChange, false);
    document.removeEventListener('keydown', handleKeyboardShortcut, false);

    unbindSystemPreferenceListener();
  }

  /**
   * Mutation observer:
   * If new toggle/buttons are loaded later by AJAX,
   * update them automatically.
   */
  function setupMutationObserver() {
    if (!window.MutationObserver) return;

    var observer = new MutationObserver(function onMutation(mutations) {
      var shouldUpdate = false;

      mutations.forEach(function checkMutation(mutation) {
        if (mutation.type !== 'childList') return;

        if (mutation.addedNodes && mutation.addedNodes.length) {
          shouldUpdate = true;
        }
      });

      if (shouldUpdate) {
        updateControls(state.currentMode, state.resolvedMode);
      }
    });

    observer.observe(document.documentElement, {
      childList: true,
      subtree: true
    });

    state.observers.push(observer);
  }

  function disconnectObservers() {
    state.observers.forEach(function disconnect(observer) {
      try {
        observer.disconnect();
      } catch (error) {}
    });

    state.observers = [];
  }

  /**
   * Public API.
   */
  var API = {
    config: CONFIG,

    state: state,

    init: function init(customConfig) {
      if (state.initialized) {
        updateControls(state.currentMode, state.resolvedMode);
        return API;
      }

      if (customConfig && typeof customConfig === 'object') {
        Object.keys(customConfig).forEach(function mergeConfig(key) {
          CONFIG[key] = customConfig[key];
        });
      }

      var initialMode = getInitialMode();

      applyTheme(initialMode, {
        source: 'init',
        save: false,
        disableTransition: true
      });

      bindEvents();
      setupMutationObserver();

      state.initialized = true;

      dispatchThemeEvent('ready', {
        mode: state.currentMode,
        resolvedMode: state.resolvedMode,
        timestamp: Date.now()
      });

      log('Initialized');

      return API;
    },

    destroy: function destroy() {
      unbindEvents();
      disconnectObservers();

      state.initialized = false;

      dispatchThemeEvent('destroy', {
        timestamp: Date.now()
      });

      return API;
    },

    set: function set(mode) {
      return setMode(mode, {
        source: 'api-set'
      });
    },

    setMode: function setModeAlias(mode) {
      return setMode(mode, {
        source: 'api-set-mode'
      });
    },

    light: function light() {
      return setMode('light', {
        source: 'api-light'
      });
    },

    dark: function dark() {
      return setMode('dark', {
        source: 'api-dark'
      });
    },

    system: function system() {
      return setMode('system', {
        source: 'api-system'
      });
    },

    toggle: function toggle() {
      return toggleMode({
        source: 'api-toggle'
      });
    },

    cycle: function cycle() {
      return cycleMode({
        source: 'api-cycle'
      });
    },

    reset: function reset() {
      return resetMode({
        source: 'api-reset'
      });
    },

    getMode: function getMode() {
      return state.currentMode;
    },

    getResolvedMode: function getResolvedMode() {
      return state.resolvedMode;
    },

    getSystemPreference: getSystemPreference,

    isDark: isDark,

    isLight: isLight,

    isSystem: isSystem,

    on: on,

    off: off,

    refresh: function refresh() {
      updateControls(state.currentMode, state.resolvedMode);
      return API;
    }
  };

  /**
   * Attach to RX Theme namespace.
   */
  RX.DarkMode = API;
  RX.darkMode = API;

  /**
   * Early theme apply:
   * This reduces flash between light/dark during loading.
   */
  try {
    var earlyMode = getInitialMode();
    var earlyResolved = resolveMode(earlyMode);

    applyHtmlAttributes(earlyMode, earlyResolved);
    updateMetaThemeColor(earlyResolved);
    updateColorScheme(earlyResolved);

    state.currentMode = earlyMode;
    state.resolvedMode = earlyResolved;
  } catch (error) {}

  /**
   * DOM ready helper.
   */
  function domReady(callback) {
    if (
      document.readyState === 'interactive' ||
      document.readyState === 'complete'
    ) {
      callback();
    } else {
      document.addEventListener('DOMContentLoaded', callback, {
        once: true
      });
    }
  }

  domReady(function startDarkModeChunk() {
    API.init();
  });

})(window, document);

Use these HTML controls anywhere in your theme:

<button type="button" data-rx-dark-toggle>
  <span data-rx-theme-icon="light">☀️</span>
  <span data-rx-theme-icon="dark">🌙</span>
  <span data-rx-theme-label="current">Theme</span>
</button>

For three buttons:

<button type="button" data-rx-theme-button="light">Light</button>
<button type="button" data-rx-theme-button="dark">Dark</button>
<button type="button" data-rx-theme-button="system">System</button>

For select dropdown:

<select data-rx-theme-select aria-label="Choose theme mode">
  <option value="light">Light</option>
  <option value="dark">Dark</option>
  <option value="system">System</option>
</select>

Add this small CSS support in your main CSS:

html[data-rx-theme="light"] {
  color-scheme: light;
}

html[data-rx-theme="dark"] {
  color-scheme: dark;
}

html.rx-theme-no-transition *,
html.rx-theme-no-transition *::before,
html.rx-theme-no-transition *::after {
  transition: none !important;
}

html.rx-theme-transition body {
  transition:
    background-color 250ms ease,
    color 250ms ease,
    border-color 250ms ease;
}

[data-rx-theme-icon] {
  display: none;
}

[data-rx-theme-icon].is-active {
  display: inline-flex;
}

[data-rx-theme-button].is-active,
[data-rx-dark-toggle].is-active {
  font-weight: 700;
}

Best CSS variable system for RX Theme:

:root {
  --rx-color-bg: #ffffff;
  --rx-color-surface: #f8fafc;
  --rx-color-text: #111827;
  --rx-color-muted: #64748b;
  --rx-color-border: #e5e7eb;
  --rx-color-primary: #2563eb;
}

html[data-rx-theme="dark"] {
  --rx-color-bg: #0b0f19;
  --rx-color-surface: #111827;
  --rx-color-text: #f9fafb;
  --rx-color-muted: #94a3b8;
  --rx-color-border: #243244;
  --rx-color-primary: #60a5fa;
}

body {
  background: var(--rx-color-bg);
  color: var(--rx-color-text);
}

Then enqueue it after your main JS or as a static chunk:

wp_enqueue_script(
    'rx-theme-dark-mode',
    get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-005-dark-mode.js',
    array(),
    wp_get_theme()->get('Version'),
    true
);

For better anti-flash performance, load this file early in the header later, but for now this normal enqueue is safe.

Leave a Reply

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