WordPress

dark mode without plugin

Credit goes to Uriel Soto. I found his video on YouTube. The solution I show here is copied from his video and blog, I just made a few adjustments and wrote more detailed instructions. His solution was the only one I found that really worked. I also tried a few plugins, the best for me was Darklup, but I still wanted to be more flexible and have more control and understanding of what actually happens when the toggle checkbox is clicked. I still use Darklup, but only for a dark mode in the WordPress admin dashboard. Here you can find an overview of all plugins I am using: WordPress: best tools, cost overview and tips To understand what for JavaScript actually is responsible and why you need it for a dark mode, I would like to recommend this YouTube video: JavaScript Basics for WordPress

Use the Elementor HTML widget to create a clickable toggle checkbox. See below for the HTML and CSS code.

The result with the moon icon will look like this:

HTML
				
					<label class="switch">
  <input type="checkbox" id="darkmode-checkbox" unchecked>
  <span class="slider round"></span>
</label>
				
			
CSS - With moon icon
				
					/* Your selector here */
selector .switch {
  position: relative;
  display: inline-block;
  width: 50px;
  height: 26px;
}

selector .switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

selector .slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: grey; /* Sun color */
  border: 1px solid #FFF; /* White border */
  -webkit-transition: .4s;
  transition: .4s;
}


selector .slider:before {
  position: absolute;
  content: "";
  height: 18px;
  width: 18px;
  left: 2px;
  bottom: 3px;
  background-color: white;
  -webkit-transition: .2s;
  transition: .2s;
}

selector .slider::before {
  position: absolute;
  content: ""; /* Unicode sun symbol Alternative 2600, 263C,\2B24, 25CB */
  font-size: 8px;
  left: 4px;
  bottom: 3px;
  color: #FFF; /* White color for sun */
  opacity: 1; /* Show the sun by default */
  transition: opacity .2s ease, transform .2s ease; /* Smooth transitions */
}


selector .slider::after {
  position: absolute;
  content: "\263E"; /* Unicode moon symbol */
  font-size: 20px;
  font-weight: bold;
  left: 27px; /* Adjust the position for the moon */
  bottom: 3px;
  color: #FFF; /* White color for moon */
  opacity: 0; /* Hide the moon by default */
  transition: opacity .2s ease, transform .2s ease; /* Smooth transitions */
}

selector input:checked + .slider {
  background-color: #222; /* Dark background for moon */
}

selector input:checked + .slider::before {
  opacity: 0; /* Hide the sun when checked */
}

selector input:checked + .slider::after {
  opacity: 1; /* Show the moon when checked */
}

/* Rounded sliders */
selector .slider.round {
  border-radius: 500px;
}

selector .slider.round::before {
  border-radius: 500px;
}

selector .slider.round::after {
  border-radius: 0px;
}
				
			
CSS - Without icon
				
					selector .switch {
  position: relative;
  display: inline-block;
  width: 50px;
  height: 26px;
}

selector .switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

selector .slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: grey;
  border: 1px solid white;
  -webkit-transition: .4s;
  transition: .4s;
}

selector .slider:before {
  position: absolute;
  content: "";
  height: 18px;
  width: 18px;
  left: 2px;
  bottom: 3px;
  background-color: white;
  -webkit-transition: .2s;
  transition: .2s;
}

selector input:checked + .slider {
  background-color: black;
}

selector input:focus + .slider {
  box-shadow: 0 0 1px #2196F3;
}

selector input:checked + .slider:before {
  -webkit-transform: translateX(26px);
  -ms-transform: translateX(26px);
  transform: translateX(26px);
}

/* Rounded sliders */
selector .slider.round {
  border-radius: 34px;
}

selector .slider.round:before {
  border-radius: 50%;
}
				
			

You will need to install a plugin to store the javascript, which you can see in the next step. I also tried adding the code directly to the functions.php file of my hello-elementor theme, as well as uploading the .js file to the theme and then executing it with “wp-enqueue_script”. But I did not choose this solution and I would not recommend modifying the functions.php file directly.

The following plugins worked for me:

The following solutions have not worked for me:

  • Elementor Pro Custom Code 
  • Simple Custom CSS and JS
  • WP Headers And Footers

I decided to use WPCode Lite

JavaScript
				
					    ;(function(){
let chck_if_gsap_loaded = setInterval(function(){
        const eleBuilder = document.querySelector('body').classList.contains("elementor-editor-active");
   if(window.gsap && window.ScrollTrigger && !eleBuilder){
        gsap.registerPlugin(ScrollTrigger);
        light_dark_mode();
        clearInterval(chck_if_gsap_loaded);
    }
}, 500);

function light_dark_mode(){

const darkmode = gsap.timeline({paused:true});

const tl = gsap.timeline();

//tl.to('body', {backgroundColor: '#000', duration:0.00}); // Body Background color
tl.to(".dm-container-1", {backgroundColor: '#07090A', duration:0.1}, "<"); // Container Type 1 Background color
tl.to(".dm-container-2", {backgroundColor: '#30353A', duration:0.1}, "<"); // Container Type 2 Background color	
tl.to(".dm-container-3", {backgroundColor: '#404040', duration:0.1}, "<"); // Container Type 3 Background color
tl.to(".dm-container-border", {borderColor: '#a9acb4', duration:0.1}, "<"); // Container Border color
tl.to(".dm-container-gradient", {background: 'linear-gradient(140deg, #090727 400px, #8d0022 70%)', duration:0}); // Container with gradient fill
tl.to(".dm-image-brightness", { filter: "brightness(0.9)", duration:0.1}, "<"); // Reduce image brightness

tl.to(".dm-icon svg", {fill: '#ffffff', duration:0.1}, "<"); // Change icon SVG color
tl.to(".dm-icon i", {color: '#ffffff', duration:0.1}, "<"); // Change icon i color	
	
tl.to(".dm-text h1", {color: '#ffffff', duration:0.1}, "<"); // Change Text color
tl.to(".dm-text h2", {color: '#ffffff', duration:0.1}, "<");
tl.to(".dm-text h3", {color: '#ffffff', duration:0.1}, "<");
tl.to(".dm-text h4", {color: '#ffffff', duration:0.1}, "<");
tl.to(".dm-text p", {color: '#ffffff', duration:0.1}, "<");
tl.set(".dm-text a", {color: '#ffffff', duration:0.1}, "<");
tl.set(".dm-text span", {color: '#ffffff', duration:0.1}, "<");	 // Specific Text color class
tl.to(".dm-text div", {color: '#ffffff', duration:0.1}, "<");

darkmode.add(tl);

// Getting the checkbox by the Id
const toggleCheckbox = document.getElementById("darkmode-checkbox");

// Function to set the theme
function setTheme(theme) {
    if (theme === "dark") {
        // Apply dark theme styles here
        document.body.classList.add("darkmode"); // Add a class to the body for dark mode styles
        toggleCheckbox.checked = true; // Check the checkbox for dark mode
        darkmode.play(); // Assuming darkmode.play() handles theme transition
    } else {
        // Apply light theme styles here
        document.body.classList.remove("darkmode"); // Remove the class for light mode styles
        toggleCheckbox.checked = false; // Uncheck the checkbox for light mode
        darkmode.reverse(); // Assuming darkmode.reverse() handles theme transition
    }
}

// Function to initialize theme based on localStorage
function initializeTheme() {
    let currentTheme = localStorage.getItem("theme");
    if (currentTheme) {
        setTheme(currentTheme);
        // Update the checkbox state based on the current theme
        if (currentTheme === "dark") {
            toggleCheckbox.checked = true;
        } else {
            toggleCheckbox.checked = false;
        }
    } else {
        // If no theme is set in localStorage, default to light mode
        setTheme("light");
    }
}

// Initialize the theme when the page loads
initializeTheme();

// Event listener for the checkbox toggle
toggleCheckbox.addEventListener("click", () => {
    if (toggleCheckbox.checked) {
        setTheme("dark");
        localStorage.setItem("theme", "dark");
    } else {
        setTheme("light");
        localStorage.setItem("theme", "light");
    }
});




}

})();

</script>
<script data-phast-no-defer type="javascript/blocked" data-wpmeteor-type="text/javascript"  data-wpmeteor-src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.0/gsap.min.js"></script>
<script data-phast-no-defer type="javascript/blocked" data-wpmeteor-type="text/javascript"  data-wpmeteor-src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.0/ScrollTrigger.min.js">
				
			

In the JavaScript, the classes are defined as “dm-[name]”. This has the advantage that you can create different colours for the same element in elementor. For example, container type 1 can get black and container type 2 can get dark grey after pressing the dark mode toggle. Not all containers need to have the same behaviour. Just add different CSS classes that you define in the javascript above.

For the accordion widgets I added “dm-text dm-icon” to change the icon colour as well as the text.

By pressing F12 in Chrome it is possible to use the console to get live information about the toggle checkbox or the active theme. This can help when you need to debug the dark mode javascript

				
					const c1 = document.getElementById("darkmode-checkbox").checked
const c2 = localStorage.getItem("theme")
const c3 = document.getElementById("darkmode-checkbox").classList
console.log("c1:", c1, "c2:", c2, "c3:", c3);
				
			

There is a bug I was not able to fix until now. If, for example, the user switches to the non-default theme on my page, in my case the light theme, and then reloads the page, the default theme is loaded first and half a second later, it automatically switches to the desired light mode. It’s flickering.

I think it has something to do with when the JavaScript code is executed. I was not able to execute the JavaScript first and then render the website.

I found the twenty-twenty-one theme which has a dark mode toggle included. I analysed the dark theme implementation but had to realise that it is more complex than just adding one javascript file. I think the two relevant files are:

  • 1.8/classes/class-twenty-twenty-one-dark-mode.php
  • 1.8/assets/js/dark-mode-toggler.js

There are different solutions you can find, but none of them worked for me:

  • https://dev.to/ayc0/light-dark-mode-avoid-flickering-on-reload-1567
  • https://dev.to/tusharshahi/react-nextjs-dark-mode-theme-switcher-how-i-fixed-my-flicker-problem-5b54
  • https://maiobarbero.dev/articles/astro-flickering-dark-mode/
  • https://axellarsson.com/blog/astrojs-prevent-dark-mode-flicker/
  • https://codepen.io/ayc0/pen/KKWZNMW
  • https://laracasts.com/discuss/channels/javascript/darkmode-flickering
  • https://mui.com/joy-ui/main-features/dark-mode-optimization/