Note: While this tutorial is focused on Svelte, it should be possible to make it work with other UI frameworks as well. Feel free to give it a try!
You might have noticed that light/dark mode toggles are everywhere right now - pretty much every popular website allows users to choose which theme they prefer. So how do you add a theme toggle to your Svelte app?
Luckily, its pretty easy - here's what you need to do:
1. Add a Toggle Component
Create a new component, DarkModeToggle.svelte
, and add a new checkbox inside it:
<input type="checkbox" on:click={toggleTheme} />
Checkboxes are pretty good choices for toggles like this one because they are widely supported and represent Boolean states. In our case, false
represents "light off" (= "dark"), while true
represents "on".
Append the new component to your app.
2. Toggle Themes
Next, we need to implement the toggleTheme
handler. Add a script
tag to your component:
<script>
const STORAGE_KEY = 'theme';
const DARK_PREFERENCE = '(prefers-color-scheme: dark)';
const THEMES = {
DARK: 'dark',
LIGHT: 'light',
};
const prefersDarkThemes = () => window.matchMedia(DARK_PREFERENCE).matches;
const toggleTheme = () => {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
// clear storage
localStorage.removeItem(STORAGE_KEY);
} else {
// store opposite of preference
localStorage.setItem(STORAGE_KEY, prefersDarkThemes() ? THEMES.LIGHT : THEMES.DARK);
}
// TODO: apply new theme
};
</script>
...
As you can see, there's quite a lot going on in here:
- We use
localStorage
to store a user's theme preference. - We use a media query to figure out whether the user has a theme preference set in their OS.
- If the user generally prefers dark themes and also picks the dark theme on our website, we do not need to store a preference. The same is true for users who do not use dark themes both generally and on our site.
For all others, we store their preference in their
localStorage
.
3. Apply the Theme
Finally, we need to apply the new theme. Add another function to your file and call it at the end of toggleTheme
:
const applyTheme = () => {
const preferredTheme = prefersDarkThemes() ? THEMES.DARK : THEMES.LIGHT;
currentTheme = localStorage.getItem(STORAGE_KEY) ?? preferredTheme;
if (currentTheme === THEMES.DARK) {
document.body.classList.remove(THEMES.LIGHT);
document.body.classList.add(THEMES.DARK);
} else {
document.body.classList.remove(THEMES.DARK);
document.body.classList.add(THEMES.LIGHT);
}
};
const toggleTheme = () => {
...
applyTheme();
};
Looks good so far! However, we still don't handle some edge cases:
- How do we apply the initial theme when a user first visits our website?
- What happens when a user changes their system wide theme preference?
We can take care of both situations in onMount
:
import { onMount } from 'svelte';
...
onMount(() => {
applyTheme();
window.matchMedia(DARK_PREFERENCE).addEventListener('change', applyTheme);
});
Last but not least, adapt the checkbox so it's state matches the theme:
<input type="checkbox" checked={currentTheme !== THEMES.DARK} on:click={toggleTheme} />
Now, all that's left to do is:
4. Write Custom CSS
We can now write custom styles for dark mode:
@media (prefers-color-scheme: dark) {
body:not(.light) {
/* Your stuff here... */
}
}
body.dark {
/* And also here... */
}
Aaaaand we're done! π
If you are interested in a fully working example, check out my webapp Manicure (source code). It might also give you some ideas how to style the toggle.
Top comments (4)
Great article. Iβd recommend to remove the event listener when the component is destroyed. This can be done in a returned function in the onMount.
Thanks for the hint! I'll update it π
Great article. I recommend you add the file names that you put the code in.
Great article. I think in the apply theme function snippet you have repeated the declaration for the current theme variable.
Also, thank you very much, your article has helped me a lot