CODE , FRAMEWORK , FRONTEND

Pico CSS Simple Theme Toggle

    4.1 minute read  

Pico CSS is a classless (or almost classless and very minimal) CSS framework for building simple content driven sites.

i.e. Bootstrap if it was made my Linux's suckless.

Pico CSS does have a built-in dark mode theme or scheme, and it uses the settings from your web browser by default.

Firefox Themes

Those on Firefox may expect their OS dark mode to inform the browser dark mode setting... This is not always the case. Your browser theme may also need to be set.

Although Pico's documentation does explain the dark mode scheme, it's not super forth-coming with how to allow the user to dynamically change it using JavaScript.

After digging though their examples and discussion #381 on their Github. It looks like there's a small JavaScript file provided with a dropdown element (which also respects the browsers default choice), and even a JS one-liner (that does not respect the default scheme).

The Pico CSS website itself uses this somewhat hidden theme-switcher.js code plus a dependency on "theme-toggles" from toggles.dev.

But, I wanted something with a bit more functionality but with fewer dependencies...

Pico CSS Emoji Theme Toggle

I modified their theme-switcher.js to include a toggle function, and used the CSS colour variable --pico-contrast to style a bank-and-white (uncoloured?) emoji known as "Black Sun with Rays" or U+2600.

No need to toggle the SVG/image sprites when you can use CSS vars to colour an emoji.

In your sites' navigation, or where ever you'd like the dark mode setting, use the following in your HTML:

<a href="#" data-theme-switcher="toggle" title="Toggle Theme" style="color: var(--pico-contrast); font-size: 1.5em;"></a>

Then for the actual theme switcher JavaScript in theme-switcher.js:

// https://benhoskins.dev/pico-css-theme-toggle/
const themeSwitcher = {
  // Config
  _scheme: "auto",
  buttonsTarget: "a[data-theme-switcher]",
  buttonAttribute: "data-theme-switcher",
  rootAttribute: "data-theme",
  localStorageKey: "picoPreferredColorScheme",

  // Init
  init() {
    this.scheme = this.schemeFromLocalStorage;
    this.initSwitchers();
  },

  // Get color scheme from local storage
  get schemeFromLocalStorage() {
    return window.localStorage?.getItem(this.localStorageKey) ?? this._scheme;
  },

  // Preferred color scheme
  get preferredColorScheme() {
    return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
  },

  // Init switchers
  initSwitchers() {
    const buttons = document.querySelectorAll(this.buttonsTarget);
    buttons.forEach((button) => {
      button.addEventListener(
        "click",
        (event) => {
          event.preventDefault();
          // Set scheme
          this.scheme = button.getAttribute(this.buttonAttribute);
        },
        false
      );
    });
  },

  // Set scheme
  set scheme(scheme) {
    if (scheme == "auto") {
      this._scheme = this.preferredColorScheme;
    } 
    else if (scheme == "toggle") {
      this._scheme = document.documentElement.getAttribute('data-theme') === 'light' ? 'dark' : 'light';
    }
    else if (scheme == "dark" || scheme == "light") {
      this._scheme = scheme;
    }
    this.applyScheme();
    this.schemeToLocalStorage();
  },

  // Get scheme
  get scheme() {
    return this._scheme;
  },

  // Apply scheme
  applyScheme() {
    document.querySelector("html")?.setAttribute(this.rootAttribute, this.scheme);
  },

  // Store scheme to local storage
  schemeToLocalStorage() {
    window.localStorage?.setItem(this.localStorageKey, this.scheme);
  },
};

// Init
themeSwitcher.init();

This theme switcher code looks for the browser default setting first, then and uses the toggle link to change as wanted.

It sets a data attribute on the HTML element, and uses local storage instead of cookies to persist any toggle overrides across pages.

As with the original theme switcher JS, you can also use dedicated "light", "dark" and "auto" as well as the "toggle" option I use in the HTML.


Comments & Questions

Reply by email to send in your thoughts.

Comments may be featured here unless you say otherwise. You can encrypt emails with PGP too, learn more about my email replies here.

PGP: 9ba2c5570aec2933970053e7967775cb1020ef23

Recent posts