How to add dark mode to your NextJS site with Tailwind

Adding dark mode to your site in just 15 minutes using next-themes.


Before we start, we ask the question: why dark mode?

The myth that dark mode is better for the eyes has been dispelled by numerous studies.

That said, dark mode is still all the rage these days, with many surveys reporting that users predominantly prefer to use dark themes.

And we all know that developers love dark mode.

meme

Great, so how do I implement dark mode?

There's many ways to go about implementing a dark mode, in this article, we'll be using next-themes, a package that makes setting up dark mode a breeze.

A dark theme requires a few ingredients:

  1. The state: what is the current theme?
  2. A way to toggle the state: usually in a form of a button, but we'll also take a look at how to get the user's system theme.
  3. Styling the dark mode itself: we'll achieve this with the help of TailwindCSS.

Next-themes

The easiest way to implement dark mode is through the use of a package called next-themes.

We can install it by running npm install next-themes.

Once it's installed, we need to import the ThemeProvider and wrap our app with it.

_app.tsx
import { ThemeProvider } from 'next-themes'
 
function MyApp({ Component, pageProps }) {
  return (
    <ThemeProvider attribute="class" defaultTheme="system">
      <Component {...pageProps} />
    </ThemeProvider>
  )
}
 
export default MyApp

We also want to pass in attribute="class" and defaultTheme="system".

  1. attribute="class" enables us to use Tailwind to style using class-based components.
  2. defaultTheme="system" allows us to respect the user's system theme. If not, the default theme will be light.

With this setup done, we can move onto toggling and reading the current theme.

Toggling and storing the theme

In this example, we'll create a toggle in the navbar with some material design icons.

Navbar.tsx
import { MdDarkMode, MdLightMode } from 'react-icons/md'
import { useTheme } from 'next-themes'
 
const Navbar = () => {
  const { setTheme, resolvedTheme } = useTheme()
 
  return (
    <div className="navbar">
      <div className="sidebar-icon">
        {resolvedTheme == 'dark' ? (
          <MdLightMode onClick={() => setTheme('light')} size="22" />
        ) : (
          <MdDarkMode onClick={() => setTheme('dark')} size="22" />
        )}
      </div>
    </div>
  )
}

We use resolvedTheme here to account for the system theme.

We use a simple ternary to check for the resolvedTheme and render the switch accordingly.

Now, to check whether this is working, we can head into the chrome dev tools and pull up the application tab.

Under application > storage > local storage > localhost:3000, we should see a theme key with the value: light or dark, depending on the current theme.

Try toggling the button to see if it works.

toggle button

Great! Now what's left is the fun part - the styling!

Styling your dark mode

Since we're using Tailwind, we can use the very handy dark: utility to style the dark mode.

Just like the breakpoint utilities, we use the dark utility to apply classes conditionally based on the theme.

<body
  className="bg-white transition-colors dark:bg-black dark:text-neutral-300"
>
  {...}
</body>

For example, this means that body will normally have a background-color of white, and when dark mode is active, bg-black and text-neutral-300 will be applied.

The transition-colors will give a nice transition animation when the theme changes, but you can play around with Tailwind to find what you like.

And with that, dark mode is all setup! Play around with the colors to find out what you like.

It's a good idea to refer to some design guidelines to ensure accessibility.

  1. Google's material design guidelines
  2. Apple's human interface guidelines

Side notes

That's it! Thanks for reading!