Theme switch
A manual theme switch component for optional light or dark mode viewing.
Requirements (anchor)
Script (anchor)
The script needs to be loaded in the <head>
prior to the CSS to negate the FOUC when changing or refreshing pages, you can include it as a linked file or within the document as preferred.
theme-switch.js
The script is also provided uncompressed in the js
folder included with the StyleMods source files.
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();
});
It's been adapted from Adam Argyle's Building a theme switch component script to apply an inline color-scheme
style attribute to the <html>
tag as follows depending on the current color-scheme.
// If light
<html lang="en" style="color-scheme: light;">
// If dark
<html lang="en" style="color-scheme: dark;">
With no CSS provided for colors the default user-agent light and dark scheme color values will be used so the script isn't reliant on specific styling to work. It also respects a users preference for light or dark mode viewing so will even work if the button isn't included, and if the button's included it remembers the selection made and still respects user preferences.
Button (anchor)
A standard HTML button with the same ID attribute as below is all that's required for the switch.
<button id="themes">Theme switch</button>
With just the button and the script you have all the functionality required to manually switch between color-schemes and still respect a users viewing preferences.
Theme switch button (anchor)
The theme switch styles provide a simple sun and moon icon button using the attributes from the script and the button. The SVG variables and icon styles used replicate those included with the StyleMods icons and can be adapted with overrides to use icon tokens if also including the icons with your custom styles. See the source code below to view the styles.
The button is the same as the one above but uses a stylised <span>
to hide the button text visually to ensure it meets basic HTML and content accessibility requirements. The span style is included with the theme switch styles at a high specificity so will only work within the button's ID and won't clash with other styles included for <span>
elements.
The styles don't include button property values so can style it as preferred. You can also customize the icon styles with overrides for the Sass variables in the source code below, see customizing for information on how to include them.
Using the module (anchor)
To use the module load the StyleMods scss
directory as follows (changing the path to suit the source files location as required) then include the Sass mixin anywhere below.
@use "stylemods/scss" as *;
@include theme-switch-css;
Include the script and button as described above.
Source code (anchor)
theme-switch.scss
// ----------------------------------------------------------
// 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