Theme Switcher

Many people spend countless hours in front of a screen, prolonged exposure to bright color may lead to eye fatigue. One of the best QoL features that we can have included in our project, is dark mode nad a simple theme switcher. The plan is to check the window.matchMedia("(prefers-color-scheme: dark) property and add a global css class to the html tag. Later, we will build a component that will allow the user to save the choosen theme in browser's Web Storage API.

Detecting user preference

Our first course of action is to find out whether the user prefers dark mode and set the styles accordingly, i'm using Tailwind here with `selector` strategy, but you can get a similar effect using plain CSS. To achieve this all we need is this simple JS script:

theme.js

        if (window.matchMedia("(prefers-color-scheme: dark)") {
          document.documentElement.classList.add("dark");
        } else {
          document.documentElement.classList.remove("dark");
        }

Next, we want to run the script before any content (IIFE) by running it in the head element. This will prevent FOUC. Additionally, we're including themeSwitcher.js, which will be implement next :

index.html

                  <html>
                    <head>
                      <script src="theme.js"></script>
                      <script src="themeSwitcher.js" type="module"></script>
                    </head>
                    <body>
                    </body>
                  </html>
              

Building interface and logic

Now that we have it running, we can start implementing switcher's functionality. We'll build a dropdown menu, that will allow the user to choose and apply their desired theme and save it in browser's local storage. With logic in place, we can now turn our attention to cosmetics, let's animate the menu by alternating it's height and implement a simple click away function.

index.html

    <header
      class="flex justify-center bg-white text-black dark:bg-black dark:text-white"
    >
      <div class="relative flex w-fitflex-col">
        <button
          class="flex h-12 w-24 flex-col items-center justify-center font-semibold uppercase transition-all hover:bg-slate-500"
          id="theme"
        >
          Theme
        </button>

        <ul
          tabindex="-1"
          id="themeMenu"
          class="absolute top-12 flex h-0 flex-col justify-between overflow-hidden bg-stone-200 dark:bg-stone-950 transition-all duration-200"
        >
          <li>
            <button
              id="os"
              class="w-24 h-12 select-none whitespace-nowrap font-mono font-semibold hover:bg-slate-500"
              type="button"
            >
              System
            </button>
          </li>
          <li>
            <button
              id="dark"
              class="w-24 h-12 select-none whitespace-nowrap font-mono font-semibold hover:bg-slate-500"
              type="button"
            >
              Dark
            </button>
          </li>
          <li>
            <button
              id="light"
              class="w-24 h-12 select-none whitespace-nowrap font-mono font-semibold hover:bg-slate-500"
              type="button"
            >
              Light
            </button>
          </li>
        </ul>
      </div>
    </header>
                    
themeSwitcher.js

                    const darkBtn = document.getElementById("dark")
                    const lightBtn = document.getElementById("light")
                    const osBtn = document.getElementById("os")

                    osBtn.addEventListener("click", ()=>{
                      localStorage.removeItem("theme");
                      if (window.matchMedia("(prefers-color-scheme: dark)")) {
                        document.documentElement.classList.add("dark");
                      } else {
                        document.documentElement.classList.remove("dark");
                      }
                    })

                    darkBtn.addEventListener("click", ()=> {
                        localStorage.setItem("theme", "dark")
                        document.documentElement.classList.add('dark')
                    })

                    lightBtn.addEventListener("click", ()=> {
                        localStorage.setItem("theme", "light")
                        document.documentElement.classList.remove('dark')
                    })

                    // theme menu

                    const themeBtn = document.getElementById("theme")
                    const themeMenu = document.getElementById("theme_header")
                    if(themeBtn){
                      themeBtn.addEventListener("click", ()=> toggleTheme())
                    }

                    const toggleTheme = () => {
                      if(themeMenu.classList.contains("h-0")){
                          themeMenu.classList.replace("h-0", "show_theme")
                        } else {
                          themeMenu.classList.replace("show_theme", "h-0")
                        }
                    }

                    // click away

                    const closeTheme = (e) => {
                        
                      if(!themeBtn.contains(e.target) && themeMenu.classList.contains("show_theme")){
                        themeMenu.classList.replace("show_theme", "h-0")
                      }
                    }  
                        
                    window.addEventListener("click", (e) => {
                      closeTheme(e)
                    })
                    
style.css

              .show_theme {
                height: 150%;
              }