Theme switch

A theme switch button component for manually switching color-schemes whilst respecting user preferences for light or dark mode viewing with optional icon switch button as used on this website.

Requires $enable-theme-switch: true; in the _configuration.scss document, the script theme-switch.js [assets/js] loaded in the <head> prior to the style sheets, and a button with the same ID attribute as below.

Script

The script has been adapted from the one used in Adam Argyle's Building a theme switch component article to use an inline style on the <html> attribute. Loading the script in the <head> prior to the CSS negates FOUC when changing or refreshing pages.

const storageKey = "theme-preference",

onClick = () => {
  (theme.value = "light" === theme.value ? "dark" : "light"), 
  setPreference();
},

getColorPreference = () => (localStorage.getItem(storageKey) 
  ? localStorage.getItem(storageKey) 
  : window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"),

setPreference = () => {
  localStorage.setItem(storageKey, theme.value),
  reflectPreference();
},

reflectPreference = () => {
  document.firstElementChild.style.setProperty("color-scheme", theme.value);
},

theme = {
  value: getColorPreference()
};

reflectPreference(),

(window.onload = () => {
  reflectPreference(), 
  document.querySelector("#themes").addEventListener("click", onClick);
}),

window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", ({matches: e}) => {
  (theme.value = e ? "dark" : "light"),
  setPreference();
});
<button id="themes">Theme switch</button>

The script automatically detects a users preference for dark mode viewing or alternatively remembers the selection made if using the switch button to change themes, it then adds inline color-scheme style attributes to the <html> tag depending on the current color-scheme.

// If light
<html lang="en" style="color-scheme: light;">

// If dark
<html lang="en" style="color-scheme: dark;">

The inline style works with the alternative light-dark() color values provided for the color-schemes variables to apply the different themes, and nullifies the color-scheme: light dark; properties included with the :where(html) styles in the typography. The button's unstyled so can be customized as required or the optional switch button can be used.

Switch button (anchor)

With $enable-switch-button: true; and $enable-theme-switch: true;

The optional switch button provides a sun and moon icon switch button as used in the site's navigation with the icon styles written to work independently if the individual icons or utility styles are not also enabled.

<button id="themes"><span class="vis-hidden">Theme switch</span></button>

The _theme-switch.scss document is included in the [styles/components] directory, the .vis-hidden class utility is optional and can be enabled with $enable-vis-hidden: true in the _configuration.scss document.

_theme-switch.scss

The button will use the default button styles if enabled but will still work if disabled.

//  ------------------------------------------------------------
//  Theme switch button
//  ------------------------------------------------------------
@use "../../configuration" as *;
@use "../../properties" as *;
@use "icons" as *;

@if $enable-theme-switch and $enable-switch-button {

@if $enable-sun-svg and $enable-moon-svg {
  [style="color-scheme: light;"] #themes {
    --svg: var(--sun);
  }
  
  [style="color-scheme: dark;"] #themes {
    --svg: var(--moon);
  }   
}
@else {
  [style="color-scheme: light;"] #themes {
    --svg: #{$sun};
  }
  
  [style="color-scheme: dark;"] #themes {
    --svg: #{$moon};
  } 
}

#themes {
  --ico: var(--color);
  --ico-va: -.12em;
  --btn-px: .5rem;
}
  
#themes:before {
  @extend %icon-mask;
}

} // END [if/theme-switch]

As used in the site banner above the docs website CSS includes the following button customizations made to suit the site's banner navigation styles.

#themes {
  --fs: var(--fs-sm);
  --btn-bg: transparent;
  --btn-hover: transparent;
  --btn-bd-color: transparent;
  --btn-py: .25rem;
}