If you've ever wanted to have a simple "dark mode" toggle on your website, it can be a bit tricky because you should support three states: Dark, Light, and System. Additionally, there's keeping track of when the system theme changes (some people have auto dark mode at night).
What we can do is follow the system theme by default. If the user toggles the theme on our website, we'll set a theme
item in localStorage
to override the system theme. Then, if the user toggles the theme to match the system theme, we remove the override. The result is that the user can get the behavior they want just by switching the theme to what they want!
Here's a codepen with it in action:
You can also see my Svelte implementation of this on the Date Picker Svelte website (app.html, layout.svelte).
I've chosen dark mode as the default theme just in case there's a "Flash of incorrect theme", or FOIT. If the default theme was light
, this flash would be bright, which is particularly distracting for dark mode users. FOIT is a risk because we need to load our theme in JavaScript, but it should rarely happen.
The <head>
<!-- Default theme, for when JS is disabled -->
<html lang="en" data-theme="dark">
<head>
<script>
function detectTheme() {
var themeOverride = localStorage.getItem('theme')
if (themeOverride == 'dark' || themeOverride === 'light') {
// Override the system theme
return themeOverride
} else if (window.matchMedia('(prefers-color-scheme: light)').matches) {
// Use the system theme
return 'light'
} else {
// Default theme
return 'dark'
}
}
document.documentElement.setAttribute('data-theme', detectTheme())
</script>
<!-- other head tags -->
</head>
<!-- body -->
</html>
First of all, we have the data-theme="dark"
attribute in our <html>
element. That makes sure our theme is still specified if JavaScript is disabled.
Inside <head>
, there's our script. We want it to run as early as possible, so it's the first thing in <head>
. The script simply sets the <html>
data-theme
attribute with whatever theme it detects. It first checks the theme
item in localStorage
. If there's no theme
override, it uses the system theme or the default dark
theme.
Updating the theme
Now for the JS that takes care of updating the theme:
// Get the systemTheme using window.matchMedia
const prefersDarkMQ = matchMedia('(prefers-color-scheme: dark)')
let systemTheme = prefersDarkMQ.matches ? 'dark' : 'light'
prefersDarkMQ.onchange = (e) => {
// Keep the systemTheme variable up to date
systemTheme = e.matches ? 'dark' : 'light'
// Update the theme, as long as there's no theme override
if (localStorage.getItem('theme') === null) {
setTheme(systemTheme)
}
}
Here we get the system theme in our systemTheme
variable. When the system theme changes, we update our systemTheme
variable and, unless we have a theme set override set in localStorage
, we update the theme as well.
let theme = document.documentElement.getAttribute('data-theme') || 'dark'
Next, we make a variable for the theme, using the value from the data-theme
attribute which was set when the page loaded.
function setTheme(newTheme) {
document.documentElement.setAttribute('data-theme', newTheme)
theme = newTheme
if (newTheme === systemTheme) {
// Remove override if the user sets the theme to match the system theme
localStorage.removeItem('theme')
} else {
localStorage.setItem('theme', newTheme)
}
}
Finally, we define a function for updating the theme. It first updates the data-theme
attribute and the theme variable
. If the theme is updated to match the system theme, we remove our theme override from localStorage
. If it's updated to something else, we set the override.
Now, for your toggle button, you just need a click handler like this:
function toggleTheme() {
if (theme === 'dark') {
setTheme('light')
} else {
setTheme('dark')
}
}
For your CSS you simply select the html[data-theme]
attribute:
html[data-theme="dark"] {
--bg: black;
}
html[data-theme="light"] {
--bg: white;
}
html {
background-color: var(--bg);
}
Behavior
- What if the website is overridden to dark, and I want to change it to follow the system when the system theme is dark? Toggle the theme to match the system once you notice it's not.
- What if the website is light, and I want to change it to be overridden to dark when the system is already dark? Toggle it to dark mode. It'll follow the system, so once you notice the website changed to light mode, toggle it to dark mode again.
Top comments (0)