Aug 4, 2022

Add a dark mode toggle

A simple way to switch themes on your website which takes into account users' system preferences.

In this guide, we are using next-themes, which is a library that makes it easy to manage website themes, including taking into account users' system preferences.

Light mode
Dark mode

Set up next-theme

We first need to wrap our content in the theme provider. The most convenient place to do it is in templates. So let's create a template, say main, in the templates folder, and add the following:

import { ThemeProvider } from "next-themes"

export const Template = ({ children }) => {
  return (
    <ThemeProvider attribute="class">
      <div className="prose dark:prose-invert p-8">
        {children}
      </div>
    </ThemeProvider>)
}

Some notes:

  • We will be using Tailwind CSS for our theme definitions, and since it is based on class names, we are telling ThemeProvider to use class for theming.
  • We wrap our content inside a div with the prose and dark:prose-invert class, which are utility classes from Tailwind that sets up nice typography and color defaults.
  • To use the template on all pages, we can add the following line to our motif.json configuration:
{
  "templates": {
    "**/*": "main",
  },
  // ...
}

Create the theme switcher

Next, let's create a toggle to switch between themes.

import { useTheme } from "next-themes"
import { Moon, Sun } from "react-feather"

export const ThemeSwitcher = () => {
  const { resolvedTheme, setTheme } = useTheme()
  const [isMounted, setMounted] = useState(false)
  const toggleColorMode = () => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')

  useEffect(() => {
    setMounted(true)
  }, [])

  return (
    <>
      {isMounted && (
        <button
          className="w-4 h-4 cursor-pointer focus:outline-none text-neutral-900 dark:text-white"
          onClick={toggleColorMode}
          aria-label={`Switch to ${resolvedTheme === 'dark' ? "light" : "dark"} mode`}
        >
          {resolvedTheme === 'dark' ? <Sun /> : <Moon />}
        </button>
      )}
    </>
  )
}

Note the use of an isMounted flag, which is there to prevent React hydration mismatches.

Let's put the theme switcher in our template, so that it is included on all pages:

<ThemeProvider attribute="class">
  <div className="prose dark:prose-invert p-8">
    <ThemeSwitcher />
    {children}
  </div>
</ThemeProvider>

Configure Tailwind

We need to instruct Tailwind to use class for determining dark mode. In tailwind.config.js (located at the root of your project), add the following flag:

module.exports = {
  darkMode: 'class',
  // ...
}

Configure the theme

We are all set up. Now we just need to tell what colors/styles to use in dark mode. This is done by adding a dark: prefix to our Tailwind classes. So for instance if we want a button to have a blue background in light mode and a white background in dark mode, we can specify it as follows:

<button className="bg-blue dark:bg-white">Click me</button>

Any class can be prefixed with the dark: prefix, so we can customize more than just colors:

<button className="bg-blue dark:bg-white text-white dark:text-black rounded-sm dark:rounded-lg">Click me</button>

To change the website background, we'll want to set the style of our page body, globally in main.css:

body {
  @apply bg-white dark:bg-black;
}

That's it! We now have a dark mode toggle and a way to customize the design when it's on, to our users' delight.

If you want to try out a fully working setup, check out our Highway Docs template