Theme switch

Manually switch between color-schemes whilst still respecting user preferences for light or dark mode viewing.

Requirements

Requires the script loaded in the <head> prior to the CSS negates FOUC when changing or refreshing pages, the HTML button with the same attributes as the example below.

JS

Adapted Adam Argyle's Building a theme switch component script to use an inline style on the <html> attribute.

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 ? "true" : "false"),
  setPreference();
});
<button id="themes">Theme switch</button>

The script detects a users viewing preference plus remembers the selection made if using the switch button, 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;">

Without extra CSS the user-agent light and dark mode styles will be used and as demonstrated with some of the SassMods styles the user-agent colors can be applied with CSS color-mix() values for consistent site styling.

Using the module

Add the sassmods.scss to your custom styles as below then include the Sass mixin anywhere below.

@use "sassmods/scss/sassmods" as *;
@include theme-switch-css;

Using the framework

Enable the module in the sassmods-system.scss document as below, if required customize the styles or the Sass variables for the default property values directly in the source code (see below)

$enable-theme-switch: true;

Source

The source code is a self-contained Sass document that compiles the style modules as Sass mixins using Sass variables provided for the core property values that can be customized as described above.

_theme-switch.scss

The icon styles with the theme switch replicate those included with the icons which can also be used in conjunction with the theme switch styles if preferred.

// ---------------------------------------------------------- 
// Theme switch
// ----------------------------------------------------------
$light-scheme-icon:   url("data:image/svg+xml,<svg viewBox='0 0 16 16' fill='currentColor' xmlns='http://www.w3.org/2000/svg'><path d='m7.47 0v2.26h1.15v-2.26zm-4.9 2.19-0.812 0.8 1.62 1.6 0.812-0.8zm10.8 0-1.62 1.6 0.812 0.8 1.62-1.6zm-5.29 1.25s-4.59 0-4.59 4.52c0 4.52 4.59 4.52 4.59 4.52s4.59 0 4.59-4.52c0-4.52-4.59-4.52-4.59-4.52zm5.67 3.92v1.13h2.3v-1.13zm-13.7 0.0243v1.13h2.3v-1.13zm3.01 4-1.62 1.6 0.812 0.8 1.62-1.6zm9.99 0-0.812 0.8 1.62 1.6 0.812-0.8zm-5.54 2.36v2.26h1.15v-2.26z'/></svg>") !default;
$dark-scheme-icon:    url("data:image/svg+xml,<svg viewBox='0 0 16 16' fill='currentColor' xmlns='http://www.w3.org/2000/svg'><path d='m5.27 1c-0.533 0.0926-0.946 0.529-1.39 0.815-1.17 0.88-1.95 2.19-2.46 3.54-0.777 2.03-0.429 4.41 0.861 6.16 1.41 1.98 3.67 3.56 6.18 3.49 1.98-0.0388 3.86-0.991 5.28-2.33 0.48-0.565 0.971-1.16 1.27-1.84 0.089-0.435-0.481-0.742-0.809-0.456-1.61 0.937-3.67 1.22-5.37 0.365-1.52-0.764-2.79-2.04-3.55-3.55-0.871-1.73-0.56-3.83 0.411-5.45 0.187-0.304-0.0437-0.726-0.4-0.734z'/></svg>") !default;

@mixin theme-switch-css {

[style="color-scheme: light;"] #themes {
  --svg: #{$light-scheme-icon};
}
  
[style="color-scheme: dark;"] #themes {
  --svg: #{$dark-scheme-icon};
}

#themes:before {
  display: inline-block;
  content: "";
  block-size: 1em;
  inline-size: 1em;  
  vertical-align: -.12em;
  background-color: var(--ico, buttontext);
  mask-image: var(--svg);
  mask-repeat: no-repeat;
}

#themes span {
  position: absolute;
  block-size: 1px;
  inline-size: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

} // end theme-switch-css