Search code examples
javascripthtmlcssdarkmode

Darkmode checkbox input get checked only from the second click


When running on a browser, the switch below activates/disactivates darkmode from the first click, but get checked/un-checked only from the second click.

** Originally I used the checked property only and later added the checked attribute to try and fix the issue (as you can see- it didn't help). It acts the same also in case the JavaScript handles the checked attribute only.

The purpose is to have it work in a multiple pages site (hence the localStorage involvment).

I failed to understand why it happens.

Any suggestions?

all-pages.js:

const htmlTag = document.querySelector('html');
const switchBtn = document.querySelector('.slider');
const darkCheckbox = document.querySelector('.dark-cb');

function darkMode() {
  if (localStorage.getItem('dark') == 'true') {
    darkCheckbox.checked = true;
    darkCheckbox.setAttribute('checked', 'checked');
    htmlTag.classList.add('dark');
    switchBtn.classList.add('dark');
  } else {
    darkCheckbox.checked = false;
    darkCheckbox.removeAttribute('checked');
    htmlTag.classList.remove('dark');
    switchBtn.classList.remove('dark');
  }
}

switchBtn.addEventListener('click', function() {
  localStorage.setItem('dark', htmlTag.className.indexOf('dark') == -1);
  darkMode();
});


window.onload = darkMode();

style.css:

html {
  min-height: 100%;
}

.switch {
  position: absolute;
  top: 10px;
  left: 20px;
  width: 60px;
  height: 34px;
}


/* Hide default HTML checkbox */

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


/* The slider */

.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: skyblue;
  -webkit-transition: .4s;
  transition: .4s;
}

.slider::before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: #FFFF66;
  -webkit-transition: .4s;
  transition: .4s;
}

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

input:focus+.slider {
  box-shadow: 0 0 1000px darkblue;
}

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


/* Rounded sliders */

.slider.round {
  border-radius: 34px;
}

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

.dark {
  filter: invert(1);
}

index.html:

<html lang="en">

  <head>
    <link href="./style.css" rel="stylesheet" />
  </head>

  <body>
    <nav id="nav-bar">
      <label class="switch">
            <input type="checkbox" class='dark-cb'>
            <span class="slider round" title="light/dark mode"></span>
          </label>
    </nav>
    <script src="all-pages.js"></script>
  </body>

</html>

Solution

  • Some modification solved the issue.

    Slightly modified CSS:

    .switch input {
      opacity: 0;
      z-index: 100;
      cursor: pointer;
      width: 52px;
      height: 30px;
    }
    

    Modified JS:

    const htmlTag = document.querySelector('html');
    const switchBtn = document.querySelector('.slider');
    const darkCheckbox = document.querySelector('.dark-cb');
    
    function darkMode() {
      if (localStorage.getItem('dark') == 'true') {
        darkCheckbox.defaultChecked = true;
        htmlTag.classList.add('dark');
        switchBtn.classList.add('dark');
      } else {
        darkCheckbox.defaultChecked = false;
        htmlTag.classList.remove('dark');
        switchBtn.classList.remove('dark');
      }
    }
    
    darkCheckbox.addEventListener('click', function() {
      let isDark = (localStorage.getItem('dark') == 'true') ? 'false' : 'true';
      if (localStorage.getItem('dark') == null) {isDark = 'true';}
      localStorage.setItem('dark',isDark);
      darkMode();
    });
    
    window.onload = darkMode;
    
    1. I changed the localStorage.getItem('dark') value to be accepted by the isDark variable
    2. I used .defaultChecked instead of .checked for the input.
    3. I made the .addEventListnter() be triggered by the darkCheckbox, not switchBtn (that is why I used z-index: 100 on the CSS, to bring the input to the front).