How To Create A Themeable Static Website

This post was originally written at Freecodecamp: How to create a theme-able static website if you are a tutorial person you can go ahead and read the guide up

But if you are not, you can just copy this codes accodingly and you are good to go...

Copy codes accondingly

_data/themes.json file

```json:        
[
    {
        "id": "default",
        "colors": {
            "text": "#222126",
            "text-dark": "#777;",
            "border": "rgba(0,0,0,.1)",
            "primary": "#665df5",
            "secondary": "#6ad1e0",
            "primary-dark": "#382cf1",
            "bg": "#ffffff",
            "bg-alt": "#f8f8f8",
            "overlay": "rgba(255, 255, 255, .4)"
        }
                }, 
    ... other color schemes
]
```
        
    

theme.css.liquid file
    

permalink: '/assets/theme.css' eleventyExcludeFromCollections: true

:root { --text: {{ themes[0].colors.text }}; --text-dark: {{ themes[0].colors.text-dark }}; --border: {{ themes[0].colors.border }}; --primary: {{ themes[0].colors.primary }}; --secondary: {{ themes[0].colors.secondary }}; --primary-dark: {{ themes[0].colors.primary-dark }}; --bg: {{ themes[0].colors.bg }}; --bg-alt: {{ themes[0].colors.bg-alt }}; }
@media(prefers-color-scheme: dark) { :root { --text: {{ themes[1].colors.text }}; --text-dark: {{ themes[1].colors.text-dark }}; --border: {{ themes[1].colors.border }}; --primary: {{ themes[1].colors.primary }}; --secondary: {{ themes[1].colors.secondary }}; --primary-dark: {{ themes[1].colors.primary-dark }}; --bg: {{ themes[1].colors.bg }}; --bg-alt: {{ themes[1].colors.bg-alt }}; } }

{% for theme in themes %} [data-theme="{{ theme.id }}"] { --text: {{ theme.colors.text }}; --text-dark: {{ theme.colors.text-dark }}; --border: {{ theme.colors.border }}; --primary: {{ theme.colors.primary }}; --secondary: {{ theme.colors.secondary }}; --primary-dark: {{ theme.colors.primary-dark }}; --bg: {{ theme.colors.bg }}; --bg-alt: {{ theme.colors.bg-alt }}; } {% endfor %}

The CSS, for the UI
    
        .section-theme.active {
            right: 0%;
          }
      .section-theme {
        background: var(--bg-alt);
        border-left: 1px solid var(--border);
        position: fixed;
        top: 0;
        width: 320px;
        height: 100vh;
        overflow-y: scroll;
        z-index: 900;
        right: -9999px;
        box-shadow: 1px 0px 2px 0 rgba(60, 64, 67, 0.3),
          2px 0 6px 2px rgba(60, 64, 67, 0.15);
        transition: all 0.35s cubic-bezier(0.785, 0.135, 0.15, 0.86);
      
        &__contain {
          padding-bottom: 30px;
      
          p {
            margin-bottom: 24px;
            font-size: calc(var(--font-size) - 2px);
            color: var(--text);
          }
        }
      }
      @media (max-width: 320px) {
        .section-theme {
          width: calc(100% - 2px);
        }
      }
      .theme-title {
        font-weight: 400;
        font-size: 18px;
        margin-bottom: 5px;
      }
      .theme__header {
        padding: 30px 30px 8px 30px;
      }
      
      .theme__top {
        padding: 1px 30px 10px 30px;
      }
      
      .theme-top-col {
        border-bottom: 1px solid var(--border);
        padding: 0.9em 0;
      }
      
      .theme__bottom {
        padding: 20px 0px 30px 30px;
      }
      .theme-color-wrapper {
        display: flex;
        overflow-x: auto;
        padding: 0.5em 0;
      
        .theme-color {
          transition: all 0.3s cubic-bezier(0.785, 0.135, 0.15, 0.86);
        }
        .theme-color:not(:first-child) {
          margin-left: -30px;
        }
        .theme-color:hover ~ .theme-color {
          margin-left: 0;
        }
      }
      .theme-color {
        padding: 1.2em;
        border-radius: 11px;
        width: 200px;
        min-width: 200px;
        position: relative;
        z-index: 5;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 3px 4.5px rgba(0, 0, 0, 0.3);
      
        &__secondary {
          width: 34px;
          height: 24px;
          border-radius: 6px;
          box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
        }
        &-text {
          display: inline-block;
          width: 80px;
          height: 6px;
          margin-top: 0.4em;
          border-radius: 40em;
        }
      }
      .theme-span-wrapper {
        display: flex;
      
        span {
          width: 24px;
          height: 24px;
          border-radius: 50%;
        }
        span:not(:first-child) {
          margin-left: -10px;
        }
      }
      .js-font-btn {
        margin-top: 8px;
      }
      .js-font-btn.active {
        background: var(--primary);
        color: var(--bg);
      }
      /* styling the checkboxes */
      [role="switch"][aria-checked="true"] :first-child,
      [role="switch"][aria-checked="false"] :last-child {
        border-radius: 0.4em;
        background: var(--primary);
        color: var(--bg);
      }
      
      .btn[role="switch"] span:not(:first-child) {
        margin-left: 0.6em;
      }
      .btn[role="switch"] span {
        display: inline-block;
        padding: 0.6em;
        pointer-events: none;
      }
The Html, for the UI
    
<section class="section-theme">
  <div class="section-theme__contain">
    <div class="theme__header flex">
      <h4 class="theme__title">Theme Settings</h4>
      <button
        class="header__icon js-close-theme"
        aria-expanded="false"
        aria-controls="nav-menu"
        aria-label="toggle menu"
      >
        <svg class="icon icon--small icon--btn" width="26px" height="26px">
          <title>Close Theme Switcher</title>
          <use xlink:href="#icon-close"></use>
        </svg>
      </button>
    </div>

    <div class="theme__top">
      <div class="js-tab-content theme-top-col show" id="tab-1-content">
        <h5 class="theme-title">Fonts</h5>
        <p>Choose default font size for articles</p>

        <div class="theme-btn-wrapper js-btn-wrapper">
          <button class="btn btn--small btn--border js-font-btn" value="16">
            16px
          </button>
          <button class="btn btn--small btn--border js-font-btn" value="18">
            18px
          </button>
          <button class="btn btn--small btn--border js-font-btn" value="20">
            20px
          </button>
          <button class="btn btn--small btn--border js-font-btn" value="22">
            22px
          </button>
        </div>
      </div>
      <div class="js-tab-content theme-top-col two" id="tab-2-content">
        <h5 class="theme-title">Fixed Header</h5>
        <p>Static or Fixed Header? your choice</p>

        <div class="check-wrapper">
          <span id="btn-label" class="sr-only">Fixed or static header</span>
          <button
            role="switch"
            type="button"
            aria-checked="true"
            aria-labelledby="btn-label"
            class="js-theme-toggle btn btn--border btn--rounded btn--toggle"
          >
            <span>On</span>
            <span>Off</span>
          </button>
        </div>
      </div>
    </div>
    <div class="js-tab-content theme__bottom" id="tab-3-content">
      <h5 class="theme-title">Choose Color Scheme</h5>
      <p>Pick a color scheme of your choice</p>

      <div class="theme-color-wrapper js-theme-scheme">
        {% for themeColor in themes %}
        <button
          type="button"
          class="theme-color js-theme-color"
          style="background-color: {{ themeColor.colors.bg }}; border: 1px solid {{ themeColor.colors.bg-alt }}"
          value="{{ themeColor.id }}"
        >
          <div
            class="theme-color__secondary"
            style="background-color: {{ themeColor.colors.bg-alt }}; margin-bottom: .5em;"
          ></div>
          <span
            class="theme-color-text"
            style="background: linear-gradient(180deg, {{ themeColor.colors.text }} 100%, {{ themeColor.colors.text }} 100%); color: {{ themeColor.colors.text }}"
          ></span>
          <span
            class="theme-color-text"
            style="background: linear-gradient(180deg, {{ themeColor.colors.text }} 100%, {{ themeColor.colors.text }} 100%); color: {{ themeColor.colors.text }}"
          ></span>
          <br />
          <div class="flex">
            <span
              class="theme-color__name"
              style="color: {{ themeColor.colors.text }};"
            >
              {{ themeColor.id }}
            </span>
            <span class="theme-span-wrapper" style="margin-top: 0.5em">
              <span
                style="background-color: {{ themeColor.colors.primary }};"
              ></span>
              <span
                style="background-color: {{ themeColor.colors.secondary }};"
              ></span>
              <span
                style="background-color: {{ themeColor.colors.primary-dark }};"
              ></span>
            </span>
          </div>
        </button>
        {% endfor %}
      </div>
    </div>
  </div>
</section>
the Javascript
    
        const themeBtn = document.querySelector(".js-theme-btn");
        themeBtn.addEventListener("click", (e) => {
          const themeWrapper = document.querySelector(".section-theme");
          themeWrapper.classList.toggle("active");
        });
    const themeClose = document.querySelector(".js-close-theme");
    themeClose.addEventListener("click", (e) => {
      const themeWrapper = document.querySelector(".section-theme");
      themeWrapper.classList.remove("active");
    });
    
    class CustomTheme {
      constructor() {
        this.islocalStorage = function () {
          try {
            localStorage.setItem("test", "testing");
            localStorage.removeItem("test");
            return true;
          } catch (error) {
            return false;
          }
        };
    
        this.schemeBtns = document.querySelectorAll(".js-theme-color");
        this.schemeBtns.forEach((btn) => {
          const btnVal = btn.value;
          btn.addEventListener("click", () => this.themeScheme(btnVal));
        });
    
        this.fontBtns = document.querySelectorAll(".js-font-btn");
        this.fontBtns.forEach((btn) => {
          const btnVal = btn.value;
          const btnTag = btn;
          btn.addEventListener("click", () => this.themeFont(btnVal, btnTag));
        });
    
        this.switchBtn = document.querySelector(".js-theme-toggle");
        const clicked = this.switchBtn;
        this.switchBtn.addEventListener("click", () => this.themePosition(clicked));
      }
    
      themeScheme(btnVal) {
        document.documentElement.setAttribute("data-theme", btnVal);
        if (this.islocalStorage) {
          localStorage.setItem("theme-name", btnVal);
        }
      }
    
      themeFont(btnVal, btnTag) {
        document.documentElement.style.setProperty("--font-size", `${btnVal}px`);
        if (this.islocalStorage) {
          localStorage.setItem("font-size", btnVal);
        }
        if (btnVal == localStorage.getItem("font-size")) {
          removeActive();
          btnTag.classList.add("active");
        }
      }
    
      themePosition(clicked) {
        if (clicked.getAttribute("aria-checked") == "true") {
          clicked.setAttribute("aria-checked", "false");
          document.documentElement.style.setProperty("--position", "static");
          document.documentElement.style.setProperty("--top-margin", "0px");
          if (this.islocalStorage) {
            localStorage.setItem("position", "static");
          }
        } else {
          clicked.setAttribute("aria-checked", "true");
          document.documentElement.style.setProperty("--position", "fixed");
          document.documentElement.style.setProperty("--top-margin", "96px");
          if (this.islocalStorage) {
            localStorage.setItem("position", "fixed");
          }
        }
      }
    }
    
    function removeActive() {
      const btns = document.querySelectorAll(".js-font-btn");
      btns.forEach((btn) => {
        btn.classList.remove("active");
      });
    }
    
    if (window.CSS && CSS.supports("color", "var(--i-support")) {
      new CustomTheme();
    }
    
    window.addEventListener("load", () => {
      const fontBtns = document.querySelectorAll(".js-font-btn");
      fontBtns.forEach((btn) => {
        const btnVal = btn.value;
        const btnTag = btn;
        if (btnVal == localStorage.getItem("font-size")) {
          btnTag.classList.add("active");
        }
      });
    });

Now you have to place this piece of js at the top of your website `head` before any other css file

head js file
    
  <script>
    const scheme = localStorage.getItem("theme-name");
    document.documentElement.setAttribute("data-theme", scheme);
  
    const fontSize = localStorage.getItem("font-size");
    document.documentElement.style.setProperty("--font-size", `${fontSize}px`);
  
    const position = localStorage.getItem("position");
    if (position == "fixed") {
      document.documentElement.style.setProperty("--position", "fixed");
      document.documentElement.style.setProperty("--top-margin", "96px");
    } else {
      document.documentElement.style.setProperty("--position", "static");
      document.documentElement.style.setProperty("--top-margin", "0px");
    }
  </script>

If you copied this code and would want to know how all this work, read the guide how to create a theme-able static website