Dark / Light mode for static sites

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

Toggle icon

1<span id="toggle-dark-light" href="">
2	<img src="/img_src/nav/moon.svg" alt="light-mode" height="20" width="20">
3</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>.
1<script>
2  function showTheme() {
3    // Choose the toggle icon
4    const btn = document.getElementById("toggle-dark-light");
5    let toggleIcon = btn.firstElementChild;
6    // check the saved theme on local
7    const currentTheme = localStorage.getItem("theme");
8    // switch to that saved theme (also change toggle icon)
9    if (currentTheme === "dark") {
10      document.body.classList.toggle("dark-theme");
11      toggleIcon.src = "/img_src/nav/sun.svg";
12    } else if (currentTheme === "light") {
13      document.body.classList.toggle("light-theme");
14      toggleIcon.src = "/img_src/nav/moon.svg";
15    }
16  }
17
18  // show content after DOM loaded
19  function showContent() {
20    document.body.style.visibility = 'visible';
21    document.body.style.opacity = 1;
22  }
23
24  // listen to the DOM content loaded or not?
25  window.addEventListener('DOMContentLoaded', (event) => {
26    showTheme();
27    showContent();
28  });
29</script>

Toggling when clicking on icon

Script placed before </body>
1<script>
2  // Choose the toggle icon
3  const btn = document.getElementById("toggle-dark-light");
4  let toggleIcon = btn.firstElementChild;
5
6  // Check for dark mode preference at the OS level
7  const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");
8  // Listen to click event on toggle icon
9  btn.addEventListener("click", function () {
10    // If the user's OS setting is dark and matches our .dark-theme class...
11    if (prefersDarkScheme.matches) {
12      document.body.classList.toggle("light-theme");
13      var theme = document.body.classList.contains("light-theme")
14        ? "light"
15        : "dark";
16      toggleIconFn(theme);
17    } else {
18      // Otherwise
19      document.body.classList.toggle("dark-theme");
20      var theme = document.body.classList.contains("dark-theme")
21        ? "dark"
22        : "light";
23      toggleIconFn(theme);
24    }
25    // Save the current preferred setting
26    localStorage.setItem("theme", theme);
27  });
28
29  // Change icon
30  const toggleIconFn = (theme) => {
31    if (theme === "dark") {
32      toggleIcon.src = "/img_src/nav/sun.svg";
33    } else {
34      toggleIcon.src = "/img_src/nav/moon.svg";
35    }
36  };
37</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.
1body {
2  --text-color: #222;
3  --bkg-color: #fff;
4}
5body.dark-theme {
6  --text-color: #eee;
7  --bkg-color: #121212;
8}
9
10// Styles for users who prefer light mode at the OS level
11@media (prefers-color-scheme: dark) {
12  // defaults to light theme
13  body {
14    --text-color: #eee;
15    --bkg-color: #121212;
16  }
17  // Override dark mode with light mode styles if the user decides to swap
18  body.light-theme {
19    --text-color: #222;
20    --bkg-color: #fff;
21  }
22}

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!
1// ./main.scss
2// containing the definitions of colors
3
4$text-color: #222;
5$text-color-dark: #eee;
6
7$bkg-color: #fff;
8$bkg-color-dark: #121212;
9
10@import "./mixin";
11@import "./dark-light";
1// ./_mixin.scss
2// containing the setting up for dark/light
3
4@mixin light-vars() {
5  --text-color: #{$text-color};
6  --bkg-color: #{$bkg-color};
7}
8
9@mixin dark-vars() {
10  --text-color: #{$text-color-dark};
11  --bkg-color: #{$bkg-color-dark};
12}
1// ./_dark-light.scss
2// where to apply the colors
3
4body {
5	@include light-vars();
6}
7
8body.dark-theme {
9	@include dark-vars();
10}
11
12// Styles for users who prefer light mode at the OS level
13@media (prefers-color-scheme: dark) {
14  /* defaults to light theme */
15  body {
16    @include light-vars();
17  }
18  /* Override dark mode with light mode styles if the user decides to swap */
19  body.light-theme {
20    @include dark-vars();
21  }
22}

References