This note is for a general SSG. I use this method on this site (11ty).

Toggle icon

<span id="toggle-dark-light" href="">
	<img src="/img_src/nav/moon.svg" alt="light-mode" height="20" width="20">
</span>

Preventing flash loading

"Cloak trick" to prevent flash load for multiple sites (because the javascripts always come behind the html/css).

💡 Idea: wait for DOM loaded + apply dark them bedhind the scene and then show the site without flashing between light/dark mode.

This script placed right after <body>.

<script>
  function showTheme() {
    // Choose the toggle icon
    const btn = document.getElementById("toggle-dark-light");
    let toggleIcon = btn.firstElementChild;
    // check the saved theme on local
    const currentTheme = localStorage.getItem("theme");
    // switch to that saved theme (also change toggle icon)
    if (currentTheme === "dark") {
      document.body.classList.toggle("dark-theme");
      toggleIcon.src = "/img_src/nav/sun.svg";
    } else if (currentTheme === "light") {
      document.body.classList.toggle("light-theme");
      toggleIcon.src = "/img_src/nav/moon.svg";
    }
  }

  // show content after DOM loaded
  function showContent() {
    document.body.style.visibility = 'visible';
    document.body.style.opacity = 1;
  }

  // listen to the DOM content loaded or not?
  window.addEventListener('DOMContentLoaded', (event) => {
    showTheme();
    showContent();
  });
</script>

Toggling when clicking on icon

Script placed before </body>

<script>
  // Choose the toggle icon
  const btn = document.getElementById("toggle-dark-light");
  let toggleIcon = btn.firstElementChild;

  // Check for dark mode preference at the OS level
  const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");
  // Listen to click event on toggle icon
  btn.addEventListener("click", function () {
    // If the user's OS setting is dark and matches our .dark-theme class...
    if (prefersDarkScheme.matches) {
      document.body.classList.toggle("light-theme");
      var theme = document.body.classList.contains("light-theme")
        ? "light"
        : "dark";
      toggleIconFn(theme);
    } else {
      // Otherwise
      document.body.classList.toggle("dark-theme");
      var theme = document.body.classList.contains("dark-theme")
        ? "dark"
        : "light";
      toggleIconFn(theme);
    }
    // Save the current preferred setting
    localStorage.setItem("theme", theme);
  });

  // Change icon
  const toggleIconFn = (theme) => {
    if (theme === "dark") {
      toggleIcon.src = "/img_src/nav/sun.svg";
    } else {
      toggleIcon.src = "/img_src/nav/moon.svg";
    }
  };
</script>

For CSS

If you use SCSS and wanna use custom CSS variables (var(---var-name)) and SCSS variable ($var-name), please read next section.

body {
  --text-color: #222;
  --bkg-color: #fff;
}
body.dark-theme {
  --text-color: #eee;
  --bkg-color: #121212;
}

// Styles for users who prefer light mode at the OS level
@media (prefers-color-scheme: dark) {
  // defaults to light theme
  body {
    --text-color: #eee;
    --bkg-color: #121212;
  }
  // Override dark mode with light mode styles if the user decides to swap
  body.light-theme {
    --text-color: #222;
    --bkg-color: #fff;
  }
}

Using custom CSS variable & SCSS variable

In section "For CSS", we have to write again and again the color for both dark and light mode. We can use custom variables in this case!

// ./main.scss
// containing the definitions of colors

$text-color: #222;
$text-color-dark: #eee;

$bkg-color: #fff;
$bkg-color-dark: #121212;

@import "./mixin";
@import "./dark-light";
// ./_mixin.scss
// containing the setting up for dark/light

@mixin light-vars() {
  --text-color: #{$text-color};
  --bkg-color: #{$bkg-color};
}

@mixin dark-vars() {
  --text-color: #{$text-color-dark};
  --bkg-color: #{$bkg-color-dark};
}
// ./_dark-light.scss
// where to apply the colors

body {
	@include light-vars();
}

body.dark-theme {
	@include dark-vars();
}

// Styles for users who prefer light mode at the OS level
@media (prefers-color-scheme: dark) {
  /* defaults to light theme */
  body {
    @include light-vars();
  }
  /* Override dark mode with light mode styles if the user decides to swap */
  body.light-theme {
    @include dark-vars();
  }
}

References