Dark Mode with Tailwind CSS and Next.js

Posted at 24/01/2021

This quick little piece is to teach you how to implement in a very streamlined fashion dark mode in your website using Tailwind CSS and Next.js. Before getting it down, I was battling my brain out at trying to set up this. Especially with Next.js and all this server-side rendering stuff. Because of that, I was having trouble using your usual local.storage for saving the preferred theme on the browser's cache.

I got a little overwhelmed because it seemed like a way much harder task, implementing dark mode than I could've ever hoped for. But after researching all articles around and asking for help late at night from my dev friends, I got to a very, very, simple setup using these two frameworks.

Next-themes setup

Basically, it all goes down to this library. It really does what it says it does: perfect dark mode in 2 lines of code. Easy peasy. Just install it at your project using any of these two:


$ npm install next-themes # or $ yarn add next-themes

This usage setup is also available in the next-themes documentation but I'll just copy it here for instructional's sake. The next step is going to your _app.js file, which is created when setting up Next. Just wrap all of the elements in the return of your component function with the ThemeProvider just like that:


import { ThemeProvider } from 'next-themes' function MyApp({ Component, pageProps }) { return ( <ThemeProvider> <Component {...pageProps} /> </ThemeProvider> ) } export default

TailwindCSS setup

It's so quick that this won't last more than two paragraphs too. Go to your tailwind.config.js file and change the dark mode property to class, just like that:


// tailwind.config.js module.exports = { darkMode: 'class' }

You're done! Now, go back to the _app.js file and just add as an attribute to the ThemeProvider a class value:


// pages/_app.js <ThemeProvider attribute="class">

Setting up the toggle button

The last step is just setting a button up for toggling the themes whenever you want. We'll be using some of React's built-in features. It's simple, I promise you! Start by creating a DarkModeButton.js in your components folder. It may look like this by now:


import React from 'react' export default function DarkModeButton() { return ( <div> </div> ) }

Now we're going to add two React Hooks. One for changing the theme itself, in charge of toggling between classes, and another for changing the icon displayed at the button whenever the light or dark theme is on. We're also going to use a Hook provided by the next-theme library. Don't forget to import these!


import React, { useEffect, useState } from 'react'; import { useTheme } from 'next-themes'; export default function DarkModeButton() { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); useEffect(() => setMounted(true), []); return ( <div> </div> ) }

Then, we just have to create the button itself using the <button> HTML tag at the return of the function. You can style it however you want by changing the utility classes. With Tailwind, to set how you want a given element to look like, when in the dark or light mode, just add the dark prefix at the utility class, like:


className="bg-gray-200 dark:bg-black"

When in light mode, the background color will be the gray-200, and when in dark mode it will be bg-black. Couldn't be easier :) This is the styling I've chosen for my button. It also includes the hover state.


import React, { useEffect, useState } from 'react'; import { useTheme } from 'next-themes'; export default function DarkModeButton() { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); useEffect(() => setMounted(true), []); return ( <button aria-label="Toggle Dark Mode" type="button" className="bg-gray-200 hover:bg-gray-300 dark:bg-gray-800 dark:hover:bg-gray-700 transition-all rounded flex items-center justify-center h-7 w-7"> </button> ); }

Getting at the final steps, we just have to add an onClick event to the button, passing to it the setTheme hook we've previously declared in the function. And then, we can also use the mounted hook for changing the icons.


import React, { useEffect, useState } from 'react'; import { useTheme } from 'next-themes'; export default function DarkModeButton() { const [mounted, setMounted] = useState(false); const { theme, setTheme } = useTheme(); useEffect(() => setMounted(true), []); return ( <button aria-label="Toggle Dark Mode" type="button" className="bg-gray-200 hover:bg-gray-300 dark:bg-gray-800 dark:hover:bg-gray-700 transition-all rounded flex items-center justify-center h-7 w-7" onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} > {mounted && ( <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="h-4 w-4 text-gray-800 dark:text-gray-200" > {theme === 'dark' ? ( <path fillRule="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" clipRule="evenodd" /> ) : ( <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" /> )} </svg> )} </button> ); }

I'm using a sun and moon icon for representing light and dark themes. When in the dark theme, the sun icon is displayed indicating that, when clicking on it, the theme will change to the light one.

The mounted hook is set at false by default, so your website will, at first load, be in light mode. If you want to change that, just swap it to true instead. You can also change the <path> element to display different icons if you want it. The first <path> rendered is the icon displayed when in the dark mode.

This implementation also avoids a common issue with dark mode which is the flashy-ness. This is a simple yet elegant way of doing it. Tailwind also makes it a lot easier for us to control how we want each element to look in a given theme. Instead of deciding which color, say your light gray color, goes to whenever the theme is dark, you can pinpoint in the exact component how it will render.

This is especially important since its dark mode isn't only getting colors to the opposite side of the light spectrum. Great dark mode implementations preserve the interface depth, still displaying how components, elements, and sections relate to each other in the desired hierarchy.

Hope you dig it! I wish I had found some article like this one when I was freaking about on how to do this. There you have it! For the night owl's eye health sake, dark mode.

What did you think about this article?

Did you find any typos? Did something specific get your attention? Are you curious about something I went over superficially? If yes, drop me a note somewhere! I'll be happy to connect with you.