Search code examples
javascriptjquerydarkmode

jQuery - Toggle dark mode using local storage and prefers-color-scheme


I have a dark-mode function that detects the system default appearance (light, dark, auto) and changes the CSS to match the selected system theme. I also allow localStorage to remember the last selected mode when the user browses different subpages on my site.

The problem is that my function only toggles between modes while the system default is set to light mode.

How do I update the code so that when the system is set to dark mode or auto, I am able to override and toggle light mode?

$(document).ready(function() {
  if (localStorage.getItem("mode") == "dark-theme") {
    $("body").addClass("dark-theme");
  } else if (localStorage.getItem("mode") == "light-theme") {
    $("body").removeClass("dark-theme");
  }
  var mq = window.matchMedia("(prefers-color-scheme: dark)");
  if (localStorage.getItem("mode") == "light-theme") {
    $("body").removeClass("dark-theme");
  } else if (mq.matches) {
    $("body").addClass("dark-theme");
  }
});

$("#theme_toggle").on("click", function() {
  if ($("body").hasClass("dark-theme")) {
    $("body").removeClass("dark-theme");
    localStorage.setItem("mode", "light-theme");
  } else {
    $("body").addClass("dark-theme");
    localStorage.setItem("mode", "dark-theme");
  }
});
body {
  --font-color: blue;
  --bg-color: white;
}

body.dark-theme {
  --font-color: white;
  --bg-color: black;
}

@media (prefers-color-scheme: dark) {
  body {
    --font-color: white;
    --bg-color: black;
  }
  body.light-theme {
    --font-color: blue;
    --bg-color: white;
  }
}

body {
  color: var(--font-color);
  background: var(--bg-color);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<label for="theme_toggle">
  <input type="checkbox" id="theme_toggle">
  Dark mode?
</label>
<h3>Title</h3>


Solution

  • Try like this:

    $(document).ready(function() {
      //check for localStorage, add as browser preference if missing
      if (!localStorage.getItem("mode")) {
        if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
          localStorage.setItem("mode", "dark-theme");
        } else {
          localStorage.setItem("mode", "light-theme");
        }
      }
    
      //set interface to match localStorage
      if (localStorage.getItem("mode") == "dark-theme") {
        $("body").addClass("dark-theme");
        $("body").removeClass("light-theme");
        document.getElementById("theme_toggle").checked = true;
      } else {
        $("body").removeClass("dark-theme");
        $("body").addClass("light-theme");
        document.getElementById("theme_toggle").checked = false;
      }
    
      //add toggle
      $("#theme_toggle").on("click", function() {
        if ($("body").hasClass("dark-theme")) {
          $("body").removeClass("dark-theme");
          $("body").addClass("light-theme");
          localStorage.setItem("mode", "light-theme");
        } else {
          $("body").addClass("dark-theme");
          $("body").removeClass("light-theme");
          localStorage.setItem("mode", "dark-theme");
        }
      });
    });
    body {
      --font-color: blue;
      --bg-color: white;
    }
    
    body.dark-theme {
      --font-color: white;
      --bg-color: black;
    }
    
    @media (prefers-color-scheme: dark) {
      body {
        --font-color: white;
        --bg-color: black;
      }
      body.light-theme {
        --font-color: blue;
        --bg-color: white;
      }
    }
    
    body {
      color: var(--font-color);
      background: var(--bg-color);
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <label for="theme_toggle">
      <input type="checkbox" id="theme_toggle">
      Dark mode?
    </label>
    <h3>Title</h3>

    The reason the code didn't work as expected was because the second if statement was overwriting the result of the first.

    It's simpler to break this down into checking for the localStorage first and then using that to set the user interface (including the checkbox).

    If localStorage is missing (e.g. the user is visiting for the first time or has cleared localStorage) then set it to be the browser preference. The typical approach is to assume that anything other than dark, means use light.

    Toggling a .light-theme class in the code allows the CSS to apply the browser preference if the user has scripting disabled (e.g. browser says default dark mode, code has not run so there is no .light-theme --> use dark mode).