Skip to content

Instantly share code, notes, and snippets.

@gfscott
Last active Feb 21, 2022
Embed
What would you like to do?
Basic dark mode toggler using CSS variables and cookies
<!-- https://gfscott.com/blog/dark-mode-for-real/ -->
<!doctype html>
<html lang="en-CA">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>
// Get cookie value by name
function getCookie (name) {
let value = `; ${document.cookie}`;
let parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
// Set cookie key and value
function setCookie (key, value) {
document.cookie = `${key}=${value}; path=/; max-age=${60 * 60 * 24 * 365}; SameSite=Strict; Secure;`;
}
</script>
</head>
<body data-theme="light">
<!-- Toggle checkbox -->
<label id="theme" for="theme_toggler">
<input type="checkbox" id="theme_toggler" class="visuallyhidden">
</label>
<script>
const toggler = document.querySelector('#theme_toggler');
const media = window.matchMedia('(prefers-color-scheme: dark)');
const getSystemTheme = () => {
return media.matches ? "dark" : "light";
}
// Set the theme, update the UI and the cookie
const setTheme = (color) => {
setCookie('theme', color)
document.querySelector('body').dataset.theme = color;
toggler.checked = color === 'dark' ? true : false;
let other = color === 'dark' ? 'light' : 'dark';
toggler.ariaLabel = `Switch to ${other} mode`;
}
// Use cookie preference if it exists; otherwise use browser preference
if ( getCookie('theme') ) {
setTheme(getCookie('theme'))
} else {
setTheme(getSystemTheme())
}
// Listen for checkbox toggle
toggler.addEventListener('change', () => {
toggler.checked ? setTheme('dark') : setTheme('light');
});
// Listen for browser preference change
media.onchange = () => {
setTheme(getSystemTheme())
}
</script>
</body>
</html>
:root {
--hue: 215;
--sat: 50%;
--colorBgPrimary: hsl(var(--hue), var(--sat), 100%);
--colorFgPrimary: hsl(var(--hue), var(--sat), 20%);
--colorFgSecondary: hsl(var(--hue), 25%, 45%);
--colorAccentPrimary: hsl(var(--hue), calc(var(--sat) * 2), 35%);
--colorAccentSecondary: hsl(var(--hue), calc(var(--sat) * 2), 50%);
}
body[data-theme="dark"] {
--hue: 200;
--colorBgPrimary: hsl(var(--hue), var(--sat), 15%);
--colorFgPrimary: hsl(var(--hue), var(--sat), 90%);
--colorFgSecondary: hsl(var(--hue), 25%, 70%);
--colorAccentPrimary: hsl(30, calc(var(--sat) * 2), 75%);
--colorAccentSecondary: hsl(30, calc(var(--sat) * 2), 65%);
}
/* Accessibly hide elements */
.visuallyhidden {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
/* Dark Mode Toggler */
#theme {
display: grid;
place-content: center;
position: absolute;
height: 2rem;
width: 2rem;
top: 1rem;
right: 1rem;
border: solid 2px transparent;
border-radius: 3px;
}
#theme:focus-within {
border-color: var(--colorAccentPrimary);
}
body[data-theme="dark"] #theme::before {
content: "\1F506";
}
body[data-theme="light"] #theme::before {
content: "\1F312";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment