Est. reading time: 4 minutes
The recent years have shown that no site and/or app can ever be called complete without a dark mode. Imagine opening a link on your phone, in the middle of the night while lying in your bed - only to have your eyes burned out by a bright white background color 👀 Horrible thought, isn't it?
No, not genuinely.
But dark mode really is a "feature" that's high on the list of consumers, it got first class support in different OS's already and it also piqued my interest to see what it actually takes to add it here on this site 🤷♂️
I know that TailwindCSS has support for it, so I went over to the docs and did a little reading. It's not enabled by default because of file size considerations, so you need to modify your config:
module.exports = { mode: 'jit', purge: ['./components/**/*.{js,ts,jsx,tsx}', './pages/**/*.{js,ts,jsx,tsx}'], darkMode: 'class', // <-- RIGHT HERE ...
You have 2 options here - media
or class
. The difference is that media
uses the prefers-color-scheme media feature under the hood whereas class
lets you add support for toggling dark mode manually. Since I like to give the user the option, I went with class
.
So now that we updated the config to support dark mode, it's time to use it. Tailwind now generates dark
variants for color-related classes, which includes text color, background color, border color, gradients, and placeholder color. These variants will take precedence whenever the dark class is present earlier in the HTML tree
. The Tailwind docs provide you with a small code snippet to easily add/remove the dark
class to/from the DOM:
// On page load or when changing themes, best to add inline in `head` to avoid FOUCif ( localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) { document.documentElement.classList.add('dark');} else { document.documentElement.classList.remove('dark');}
// Whenever the user explicitly chooses light modelocalStorage.theme = 'light';
// Whenever the user explicitly chooses dark modelocalStorage.theme = 'dark';
// Whenever the user explicitly chooses to respect the OS preferencelocalStorage.removeItem('theme');
So once the whole thing is set up, we can use the dark
variants to tell the component how to behave in both cases. In this example, we have white background and black text while in dark mode
we have dark background with white text.
<div className="bg-white text-black dark:bg-night-500 dark:text-white"> <!-- some children here --></div>
I used the exact snippet found in the docs, added it to a useEffect
and stored the theme choice in my app's state. It's passed into the navbar component which, depending on the choice, shows a sun or a moon icon the user can click to toggle the mode. It's also stored automatically in local storage so if the user leaves and comes back later the preference is restored.
useEffect(() => { if ( localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches) ) { setTheme('dark'); } else { setTheme('light'); }}, []);
useEffect(() => { localStorage.setItem('theme', theme); if (theme === 'dark') { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); }}, [theme]);
As you can see, adding dark mode
to a Tailwind project can be done in a couple of minutes once you know how. I really like how straightforward it is using the dark
variant, which can also be stacked with other variants without an issue.
There are some specificity considerations when working with dark mode since its utilities will be higher than regular utilities because the selector contains an extra class. But you can work around that by simply re-specifying those using the dark
variant. Again the Tailwind docs got you covered on that one 👍