Tailwind Dark Mode Toggle with Nuxt & Vuex
In this post, I'll show you how to create a simple Dark Mode Toggle in Nuxt.js (also Vue.js) with Tailwind CSS in just a few steps.
Goal
The goal is to implement a simple Toggle for Light- and Dark Mode, such as in the Nuxt docs.
How Dark Mode works in Tailwind
By default, Tailwind uses the prefers-color-scheme
CSS media feature. It automatically detects if the user has requested a light or dark color theme. To learn more about this CSS media feature, you can read in the MDN documentation. You can use Tailwind's dark
variant to style your site differently when dark mode is enabled.
<div class="bg-white dark:bg-slate-900" />
But since we want to switch the dark mode manually, we will follow the class-based approach.
Strategy
The class-based approach for Dark Mode works by having the dark
class present in the HTML tree. To activate the class based strategy, we need to change the darkMode
property in the Tailwind configuration to 'class'
as following.
module.exports = {
darkMode: 'class'
};
Prepare Vuex store
To enable the toggling function, we will use the Vuex store. We will set a dark
state to not only conditionally render our class later on, but also toggle it later in our actual component.
export const state = () => ({
dark: false
});
export const getters = {
dark: (state) => state.dark
};
export const mutations = {
SET_DARK: (state, bool) => {
state.dark = bool;
}
};
Layout
Now that we have laid our foundation, we can conditionally render the dark
class into any layout. For this example, it will be the Nuxt default layout.
<template>
<div
id="app"
class="bg-white dark:bg-black"
:class="dark ? 'dark' : 'light'"
>
<Nuxt />
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters(['dark'])
}
};
</script>
Dark Mode Toggle
Now we want to implement the toggle. For this, I will create a DarkModeToggle.vue
component.
The SVG's I will use are from heroicons which are also by the makers of Tailwind CSS.
We will use a moon icon for Dark Mode and a sun icon for Light Mode. The icons are wrapped in a basic button element. The toggling is made possible by clicking on the button and the previously implemented SET_DARK
mutation.
<template>
<button @click="toggleDarkMode">
<svg
v-if="dark"
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"
/>
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
clip-rule="evenodd"
/>
</svg>
</button>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex';
export default {
computed: {
...mapGetters(['dark'])
},
methods: {
...mapMutations(['SET_DARK']),
toggleDarkMode() {
this.SET_DARK(!this.dark);
}
}
};
</script>
Save user preference
In this example, we will use localStorage
to save the user preference, as explained by Tailwind.
We will use the mounted hook to get the user preference by using the CSS media feature prefers-color-scheme
and store it in localStorage
. If the property doesn't exist, we will create one on mount.
<script>
import { mapGetters, mapMutations } from 'vuex';
export default {
computed: {
...mapGetters(['dark'])
},
mounted() {
if (localStorage.theme === undefined) {
if (
window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)')
.matches
) {
localStorage.theme = 'dark';
this.SET_DARK(true);
} else {
localStorage.theme = 'light';
}
} else {
this.SET_DARK(localStorage.theme === 'dark');
}
},
methods: {
...mapMutations(['SET_DARK']),
toggleDarkMode() {
this.SET_DARK(!this.dark);
localStorage.theme = this.dark ? 'dark' : 'light';
}
}
};
</script>
Finalize
To finish the component, we add simple styling.
<template>
<button
class="
p-2
text-slate-600 hover:text-slate-800
dark:text-slate-300 dark:hover:text-slate-100
focus-visible:ring-2 focus-visible:ring-green-400
rounded-lg
"
>...</button>
</template>