Adding Dark Mode to your Tailwind CSS website
November 30, 2020
Tailwind CSS v2.0 introduces Dark mode support and with minimal JS and inline SVG, you can allow your users to manually toggle Dark Mode. We are going to walk through what is required to build the same one from petermekhaeil.com.
Set darkMode
to class
in your config:
// tailwind.config.js
module.exports = {
darkMode: 'class'
};
What we want to do is toggle the dark
class on the <html>
element. When this class appears on your tree, any child elements below it with the dark:
will be applied.
Let’s start with a base HTML page:
<html>
<body class="bg-white text-black dark:bg-black dark:text-white">
<h1>My Website</h1>
</body>
</html>
Let’s add the UI switch using SVGs from Heroicons. We will be using the Moon and Sun SVGs. They also have IDs attached #toggle-dark
and #toggle-light
. You’ll also noticed they are hidden using the hidden
class. This is because we would like to avoid any flicker while the app decides which SVG to show (more on this later).
<html>
<body class="bg-white text-black dark:bg-black dark:text-white">
<h1>My Website</h1>
<!-- Moon SVG from Heroicons -->
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
class="hidden h-6 w-6 cursor-pointer"
id="toggle-dark">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
<!-- Sun SVG from Heroicons -->
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
class="hidden h-6 w-6 cursor-pointer"
id="toggle-light">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</body>
</html>
We will check if the user has dark mode enabled in their browser. If enabled, then toggle the correct SVG to appear and add the dark
class to <html>
:
var userPrefersDark = window.matchMedia('(prefers-color-scheme: dark)');
var toggleDark = document.getElementById('toggle-dark');
var toggleLight = document.getElementById('toggle-light');
var htmlElem = document.querySelector('html');
if (userPrefersDark.matches)) {
htmlElem.classList.add('dark');
toggleDark.classList.add('visible');
toggleLight.classList.remove('hidden');
} else {
toggleLight.classList.add('visible');
toggleDark.classList.remove('hidden');
}
Let’s now add some event handlers to the SVGs to toggle dark mode on/off:
toggleLight.addEventListener('click', function () {
localStorage.setItem('theme', 'light');
htmlElem.classList.remove('dark');
toggleDark.classList.add('visible');
toggleDark.classList.remove('hidden');
toggleLight.classList.add('hidden');
toggleLight.classList.remove('visible');
});
toggleDark.addEventListener('click', function () {
localStorage.setItem('theme', 'dark');
htmlElem.classList.add('dark');
toggleLight.classList.add('visible');
toggleLight.classList.remove('hidden');
toggleDark.classList.add('hidden');
toggleDark.classList.remove('visible');
});
One last thing we need to do is store the users preference - we want to do this otherwise when they navigate from one page to another, the setting will reset. We will use localStorage
to store the theme
. Replace the above JS blocks with this final JS:
var theme = localStorage.getItem('theme');
var userPrefersDark = window.matchMedia('(prefers-color-scheme: dark)');
var toggleDark = document.getElementById('toggle-dark');
var toggleLight = document.getElementById('toggle-light');
var htmlElem = document.querySelector('html');
if (theme === 'dark' || (!theme && userPrefersDark.matches)) {
htmlElem.classList.add('dark');
toggleDark.classList.add('visible');
toggleLight.classList.remove('hidden');
} else {
toggleLight.classList.add('visible');
toggleDark.classList.remove('hidden');
}
toggleLight.addEventListener('click', function () {
localStorage.setItem('theme', 'light');
htmlElem.classList.remove('dark');
toggleDark.classList.add('visible');
toggleDark.classList.remove('hidden');
toggleLight.classList.add('hidden');
toggleLight.classList.remove('visible');
});
toggleDark.addEventListener('click', function () {
localStorage.setItem('theme', 'dark');
htmlElem.classList.add('dark');
toggleLight.classList.add('visible');
toggleLight.classList.remove('hidden');
toggleDark.classList.add('hidden');
toggleDark.classList.remove('visible');
});
You can view the source code to see it all put together.
@tailwindcss/typography
If you use @tailwindcss/typography, you’ll need to add some configs to get dark mode working.
Add a dark:prose-dark
class. This will only work when placed next to prose
.
<article class="prose dark:prose-dark"></article>
<h1>Cannot believe adding Dark Mode is this simple!</h1>
</article>
Extend your configuration to add a dark theme:
// tailwind.config.js
module.exports = {
darkMode: 'class'
theme: {
extend: {
typography: (theme) => ({
DEFAULT: {
css: {
// ...
}
},
dark: {
css: {
color: theme('colors.gray.200')
}
}
})
}
},
variants: {
typography: ['responsive', 'dark']
},
plugins: [require('@tailwindcss/typography')]
};
If you’ve previously extended your theme for Tailwind CSS 1.x, for Tailwind CSS 2.0 you’ll need to update default theme key to DEFAULT.
You will then need to extend the rest of your styles. Use this blog’s config as an example.