This pattern shows how to go beyond light and dark themes with CSS custom properties.
Full article · Video on YouTube · Source on Github
<header>
<h3>Scheme</h3>
<form id="theme-switcher">
<div>
<input checked type="radio" id="auto" name="theme" value="auto">
<label for="auto">Auto</label>
</div>
<div>
<input type="radio" id="light" name="theme" value="light">
<label for="light">Light</label>
</div>
<div>
<input type="radio" id="dark" name="theme" value="dark">
<label for="dark">Dark</label>
</div>
<div>
<input type="radio" id="dim" name="theme" value="dim">
<label for="dim">Dim</label>
</div>
</form>
</header>
<main>
<section>
<div class="surface-samples">
<div class="surface1 rad-shadow">1</div>
<div class="surface2 rad-shadow">2</div>
<div class="surface3 rad-shadow">3</div>
<div class="surface4 rad-shadow">4</div>
</div>
</section>
<section>
<div class="text-samples">
<h1 class="text1">
<span class="swatch brand rad-shadow"></span>
Brand
</h1>
<h1 class="text1">
<span class="swatch text1 rad-shadow"></span>
Text Color 1
</h1>
<h1 class="text2">
<span class="swatch text2 rad-shadow"></span>
Text Color 2
</h1>
<br>
<p class="text1">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<p class="text2">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
</section>
</main>
* {
/* brand foundation */
--brand-hue: 200;
--brand-saturation: 100%;
--brand-lightness: 50%;
/* light */
--brand-light: hsl(var(--brand-hue) var(--brand-saturation) var(--brand-lightness));
--text1-light: hsl(var(--brand-hue) var(--brand-saturation) 10%);
--text2-light: hsl(var(--brand-hue) 30% 30%);
--surface1-light: hsl(var(--brand-hue) 25% 90%);
--surface2-light: hsl(var(--brand-hue) 20% 99%);
--surface3-light: hsl(var(--brand-hue) 20% 92%);
--surface4-light: hsl(var(--brand-hue) 20% 85%);
--surface-shadow-light: var(--brand-hue) 10% 20%;
--shadow-strength-light: .02;
/* dark */
--brand-dark: hsl(
var(--brand-hue)
calc(var(--brand-saturation) / 2)
calc(var(--brand-lightness) / 1.5)
);
--text1-dark: hsl(var(--brand-hue) 15% 85%);
--text2-dark: hsl(var(--brand-hue) 5% 65%);
--surface1-dark: hsl(var(--brand-hue) 10% 10%);
--surface2-dark: hsl(var(--brand-hue) 10% 15%);
--surface3-dark: hsl(var(--brand-hue) 5% 20%);
--surface4-dark: hsl(var(--brand-hue) 5% 25%);
--surface-shadow-dark: var(--brand-hue) 50% 3%;
--shadow-strength-dark: .8;
/* dim */
--brand-dim: hsl(
var(--brand-hue)
calc(var(--brand-saturation) / 1.25)
calc(var(--brand-lightness) / 1.25)
);
--text1-dim: hsl(var(--brand-hue) 15% 75%);
--text2-dim: hsl(var(--brand-hue) 10% 61%);
--surface1-dim: hsl(var(--brand-hue) 10% 20%);
--surface2-dim: hsl(var(--brand-hue) 10% 25%);
--surface3-dim: hsl(var(--brand-hue) 5% 30%);
--surface4-dim: hsl(var(--brand-hue) 5% 35%);
--surface-shadow-dim: var(--brand-hue) 30% 13%;
--shadow-strength-dim: .2;
}
:root {
color-scheme: light;
/* set defaults */
--brand: var(--brand-light);
--text1: var(--text1-light);
--text2: var(--text2-light);
--surface1: var(--surface1-light);
--surface2: var(--surface2-light);
--surface3: var(--surface3-light);
--surface4: var(--surface4-light);
--surface-shadow: var(--surface-shadow-light);
--shadow-strength: var(--shadow-strength-light);
}
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--brand: var(--brand-dark);
--text1: var(--text1-dark);
--text2: var(--text2-dark);
--surface1: var(--surface1-dark);
--surface2: var(--surface2-dark);
--surface3: var(--surface3-dark);
--surface4: var(--surface4-dark);
--surface-shadow: var(--surface-shadow-dark);
--shadow-strength: var(--shadow-strength-dark);
}
}
[color-scheme="light"] {
color-scheme: light;
--brand: var(--brand-light);
--text1: var(--text1-light);
--text2: var(--text2-light);
--surface1: var(--surface1-light);
--surface2: var(--surface2-light);
--surface3: var(--surface3-light);
--surface4: var(--surface4-light);
--surface-shadow: var(--surface-shadow-light);
--shadow-strength: var(--shadow-strength-light);
}
[color-scheme="dark"] {
color-scheme: dark;
--brand: var(--brand-dark);
--text1: var(--text1-dark);
--text2: var(--text2-dark);
--surface1: var(--surface1-dark);
--surface2: var(--surface2-dark);
--surface3: var(--surface3-dark);
--surface4: var(--surface4-dark);
--surface-shadow: var(--surface-shadow-dark);
--shadow-strength: var(--shadow-strength-dark);
}
[color-scheme="dim"] {
color-scheme: dark;
--brand: var(--brand-dim);
--text1: var(--text1-dim);
--text2: var(--text2-dim);
--surface1: var(--surface1-dim);
--surface2: var(--surface2-dim);
--surface3: var(--surface3-dim);
--surface4: var(--surface4-dim);
--surface-shadow: var(--surface-shadow-dim);
--shadow-strength: var(--shadow-strength-dim);
}
/* READY TO USE! */
.brand {
color: var(--brand);
background-color: var(--brand);
}
.surface1 {
background-color: var(--surface1);
color: var(--text2);
}
.surface2 {
background-color: var(--surface2);
color: var(--text2);
}
.surface3 {
background-color: var(--surface3);
color: var(--text1);
}
.surface4 {
background-color: var(--surface4);
color: var(--text1);
}
.text1 {
color: var(--text1);
}
p.text1 {
font-weight: 200;
}
.text2 {
color: var(--text2);
}
const switcher = document.querySelector('#theme-switcher')
const doc = document.firstElementChild
switcher.addEventListener('input', e =>
setTheme(e.target.value))
const setTheme = theme =>
doc.setAttribute('color-scheme', theme)