Photo by Eléonore Bommart
Allowing users to choose either a light or dark theme when viewing your website is great for accessibility. So naturally I wanted to add it to my blog. Unfortunately my blog doesn't use a framework like React or Vue, so I had to use CSS variables and JavaScript.
The CSS
First off, we need to add some CSS variables to store settings for the different themes. CSS variables are a way of storing common values in CSS, and when they are changed, they change everywhere they are referenced.
By default, the values are set for light theme. They're added to the :root
element so they're available everywhere.
:root {
--background-main: #f7fafc;
--background-panel: #edf2f7;
--text-main: #1a202c;
--opacity-light: 1;
--opacity-dark: 0;
}
Then, the dark theme versions are added. This css selector targets any element that has a data-theme
attribute set to dark
.
[data-theme='dark'] {
--background-main: #1a202c;
--background-panel: #2d3748;
--text-main: #f7fafc;
--opacity-light: 0;
--opacity-dark: 1;
}
Finally, in our CSS we can use the variables in various utility classes.
.bg-main {
background-color: var(--background-main);
}
.bg-panel {
background-color: var(--background-panel);
}
.text-main {
color: var(--text-main);
}
.theme-light {
opacity: var(--opacity-light);
}
.theme-dark {
opacity: var(--opacity-dark);
}
The HTML
The theme toggle is an anchor tag that contains two svg images, a sun and a moon, to represent the different themes.
<a id="toggle" href="#">
<svg id="moon" class="theme-light" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" /></svg>
<svg id="sun" class="theme-dark" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5" /><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" /></svg>
</a>
By using the theme-light
and theme-dark
classes the icons swap depending on the theme using opacity. To make this work the two icons need to be overlaid.
The JavaScript
So, let's handle the toggle click. We also want to store the value in local storage.
document.getElementById('toggle').addEventListener('click', function(event) {
event.preventDefault();
// Get the current theme and calculate the new theme
const currentTheme = localStorage.getItem('currentTheme') || 'light';
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
// Store the theme in local storage
localStorage.setItem('currentTheme', newTheme);
...
// Set the theme attribute
document.documentElement.setAttribute('data-theme', newTheme);
});
The site then needs to load the value when the page is reloaded.
window.addEventListener('DOMContentLoaded', function(event) {
// Load the current theme from storage
const currentTheme = localStorage.getItem('currentTheme') || 'light';
// Set the theme attribute
document.documentElement.setAttribute('data-theme', currentTheme);
});
The Transition
The last thing is to add a transition effect so the theme change is not so jarring. The transition is a CSS class that is temporarily added to the root element.
html.transition,
html.transition *,
html.transition *:before,
html.transition *:after {
transition-property: background-color color opacity !important;
transition-duration: 750ms !important;
transition-delay: 0 !important;
transition-timing-function: linear !important;
}
document.getElementById('toggle').addEventListener('click', function(event) {
// Previous code
...
// Store the theme in local storage
...
// Temporarily set a transition style
document.documentElement.classList.add('transition');
setTimeout(() => {
document.documentElement.classList.remove('transition')
}, 750);
// Set the theme attribute
...
});
The Result
See the CodePen