Dark Mode with Tailwind CSS and Next.js
Published on 24/01/2021
Last updated on 11/08/2024
This quick little piece is to help you implement, in a very streamlined fashion, a dark mode toggle in your website using Tailwind CSS and Next.js.
Before getting it down, I was battling my brain out at trying to set this up, especially given Next.js's server-side rendering stuff. I was having trouble using the usual client-side local.storage
method for saving the preferred theme on the browser's cache. I naturally got a little overwhelmed as it seemed like a much harder task than I could've ever hoped for when I initially started venturing into this.
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.
Setting up the stack
next-themes
It all goes down to this library. And it does what it says: "perfect dark mode in 2 lines of code". Easy peasy. Just install it on your project using any of these two:
1npm install next-themes
Once it is installed, go to the _app.js
file on your project, which is automatically created after installing Next, and just wrap all of the elements of the MyApp
component with the ThemeProvider
, just like so:
1import { ThemeProvider } from 'next-themes'23export default function MyApp({ Component, pageProps }) {4 return (5 <ThemeProvider>6 <Component {...pageProps} />7 </ThemeProvider>8 );9}
Tailwind CSS
First, make sure to properly install Tailwind by following their guide tailored focused on a Next.js project. Once that's done, go to your tailwind.config.js
file and change the dark mode property to class, just like that:
1module.exports = {2 darkMode: 'class'3}
Now, go back to the _app.js
file and just add "class" as the attribute value on the Theme Provider component:
1<ThemeProvider attribute="class">{...}</ThemeProvider>
Creating the theme toggle button
With everything set up, we now need to create the button that will be responsible for toggling dark mode on and off. I promise you it's not super complex! Let's start by creating a DarkModeButton.js
file in your components folder (create one if you don't have one yet).
1export default function DarkModeButton() {2 return (3 <button></button>4 );5}
We'll use hooks to add functionality. These will be basically responsible for toggling the dark
class, mounting the component and theme as a whole, and also switching the icons that we'll represent each theme within the button:
1import * as React from "react";2import { useTheme } from 'next-themes';34export default function DarkModeButton() {5 const { theme, setTheme } = useTheme();6 const [mounted, setMounted] = React.useState(false);78 React.useEffect(() => setMounted(true), []);910 return (11 <button></button>12 );13}
Now comes the fun part of actually designing the button! Don't forget to add the dark:
prefix class to other classes that should be applied to the dark mode only. Feel free to start with the styles I've added to my button.
Note that I went ahead and already added the aria-label
attribute, which is important for accessibility (enabling screen readers to properly read what the button is about).
1import * as React from "react";2import { useTheme } from 'next-themes';34export default function DarkModeButton() {5 const { theme, setTheme } = useTheme();6 const [mounted, setMounted] = React.useState(false);78 React.useEffect(() => setMounted(true), []);910 return (11 <button12 type="button"13 aria-label="Appearance mode toggle button"14 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">15 </button>16 );17}
Getting closer to the end, add the onClick
event to your button element and pass the setTheme
hook that will enable us to toggle between themes:
1<button2 type="button"3 aria-label="Appearance mode toggle button"4 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"5 onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>6</button>
Lastly, we'll now insert a sun and moon icon inside the button so the user knows which theme they'll get by clicking on it. Use whatever icons you want for this-I'm personally using Hero Icons.
1import * as React from "react";2import { useTheme } from 'next-themes';3import { SunIcon, MoonIcon } from "@heroicons/react/20/solid";45const iconStyles = "h-4 w-4 text-gray-600 dark:text-gray-300";67export default function DarkModeButton() {8 const { theme, setTheme } = useTheme();9 const [mounted, setMounted] = React.useState(false);1011 React.useEffect(() => setMounted(true), []);1213 return (14 {mounted && (15 <button16 type="button"17 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"18 aria-label={theme === "dark" ? "Toggle light mode" : "Toggle dark mode"}19 onClick={() => {20 setTheme(theme === "dark" ? "light" : "dark");21 }}22 >23 {theme === "dark" ? (24 <SunIcon className={iconStyles} />25 ) : (26 <MoonIcon className={iconStyles} />27 )}28 </button>29 )}30 );31}
Extra controls
On the Theme Provider component, use the defaultTheme
prop to determine which theme is rendered by default and the enableSystem
prop to control whether you want your application to respect the user's system appearance mode.
1<ThemeProvider2 attribute="class"3 defaultTheme="dark"4 enableSystem={false}5>6 {...}7</ThemeProvider>
Wrap up
Oof, that's it! ⚡️🎉 You should now have a functioning dark mode toggle button on your website/app. This is a particularly cool implementation as it avoids a common pitfall with dark mode out there: the annoying flashy-ness. Tailwind also makes tailoring your design for dark mode super easy. It's tough to have neat 1:1 light and dark mode color scale mapping, particularly as you will frequently need special treatment to preserve the interface's depth & hierarchy.
I hope this wasn't too hard to follow and that it helped you solve something that took me literally freaking weeks to figure out. Also, definitely visit the next-themes documentation; most of your further questions should be answered there! Enjoy! 🤙