Theme switch
A theme switch button component for manually switching color-schemes whilst respecting user preferences for light or dark mode viewing, and optional switch button as used on this website.
Requires $enable-theme-switch: true;
in the configuration.scss
document, the script theme.js
[assets/js]
loaded prior to the style sheets in the head of the HTML document (to negate FOUC when changing or refreshing pages), and a button with the same attributes as below needs to be included somewhere on the page.
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.
const storageKey = 'theme-preference'
const onClick = () => {
theme.value = theme.value === 'light'
? 'dark'
: 'light'
setPreference()
}
const getColorPreference = () => {
if (localStorage.getItem(storageKey))
return localStorage.getItem(storageKey)
else
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
}
const setPreference = () => {
localStorage.setItem(storageKey, theme.value)
reflectPreference()
}
const reflectPreference = () => {
document.firstElementChild
.style.setProperty('color-scheme', theme.value)
document.firstElementChild
.setAttribute('class', theme.value)
document
.querySelector('#themes')
?.setAttribute('aria-label', theme.value)
}
const theme = {
value: getColorPreference(),
}
reflectPreference()
window.onload = () => {
reflectPreference()
document
.querySelector('#themes')
.addEventListener('click', onClick)
}
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', ({matches:isDark}) => {
theme.value = isDark ? 'true' : 'false'
setPreference()
})
<button id="themes" aria-label="light">Theme</button>
The script alters the aria-label
attribute depending on the current theme, and adds inline styling on the page <html>
attribute to apply the alternative color-schemes. The button is provided unstyled so can be customized as required or the optional switch button can be used as described below.
Switch button (anchor)
The optional switch button provides a sun and moon icon switch button as used in the site's navigation, it requires both $enable-switch-button: true;
and $enable-theme-switch: true;
in the configuration.scss
document. The icon styles have been written to work independently if the individual icons or utility styles are not also enabled.
<button id="themes" aria-label="light"><span class="vis-hidden">theme</span></button>
The styles are provided in the _theme-switch.scss
document in the [styles/components]
directory, the optional class utility .vis-hidden
can be enabled with $enable-vis-hidden: true
in the configuration.scss
document.
_theme-switch.scss
Currently the styles still require $enable-forms-buttons: true;
for the basic button styles but the icon styling will still work with fallback user-agent styles so can be customized as required if the button styles are disabled.
// ------------------------------------------------------------
// Theme switch button
// ------------------------------------------------------------
@use "../../configuration" as *;
@use "../../properties" as *;
@use "icons" as *;
@if $enable-theme-switch {
@if $enable-sun-svg and $enable-moon-svg {
:where(html) {
--switch-off: var(--sun);
--switch-on: var(--moon);
}
}
@else {
:where(html) {
--switch-off: #{$sun};
--switch-on: #{$moon};
}
}
#themes {
--ico: var(--color);
--svg: var(--switch-ico);
--btn-px: .5rem;
--ico-va: -.12em;
}
#themes:before {
@extend %icon-mask;
}
:where(html:is(.light)) #themes {
--svg: var(--switch-off);
}
:where(html:is(.dark)) #themes {
--svg: var(--switch-on);
}
} // END [if/theme-switch]
The script also includes the alternating .light
and .dark
classes with the <html>
attribute that are used to apply the individual icons with CSS :is()
styles as demonstrated above. This method stops the icons from a small FOUC that happens if the icon styles were applied using the alternating [aria-label]
attribute.