← Back to archive

Repo of the Day

pacocoursey/next-themes

Published: Dec 8, 2024

Open repository ↗

Perfect Next.js dark mode in 2 lines of code. Support System preference and any other theme with no flashing

Summary

next-themes is a TypeScript library that adds theme support to React applications, with a focus on Next.js. It wraps the app in a ThemeProvider and applies the selected theme to the html element before the page renders, which prevents the common flash of incorrect theme that often appears with server-rendered pages. It supports any number of named themes, syncs the choice across browser tabs, and can read the operating system's color-scheme preference.

What it is useful for

This library is useful when you need to add dark mode (or any number of named themes) to a Next.js app and want correct behavior under SSR and SSG. The README documents support for both the pages/ router and the Next.js 13 app/ directory, and states it also works with Gatsby and Create React App from version 0.3.0 onward. It covers several recurring pain points:

  • Reading prefers-color-scheme so the site follows the OS theme by default.
  • Forcing a single page to a specific theme, for example a marketing page that is always dark.
  • Using the same UI with Tailwind's dark: classes by switching the attribute to class, or with any CSS-in-JS solution, because the library only manipulates a DOM attribute and leaves styling to you.
  • Avoiding the flash of wrong theme on first paint by injecting a small script that sets the attribute before React hydrates.

How engineers can use it

Installation is documented as npm install next-themes or yarn add next-themes.

For the pages/ router, wrap the app in _app.js:

import { ThemeProvider } from 'next-themes'

function MyApp({ Component, pageProps }) {
  return (
    <ThemeProvider>
      <Component {...pageProps} />
    </ThemeProvider>
  )
}
export default MyApp

For the app/ directory, place ThemeProvider inside app/layout.jsx, mark the layout as a client component, and add suppressHydrationWarning to the html element to silence the expected hydration warning.

The default attribute is data-theme, so styling is as simple as:

:root { --background: white; }
[data-theme='dark'] { --background: black; }

Components read or change the current theme with the useTheme hook. The README warns that values from useTheme are undefined on the server, so any UI that depends on the current theme must wait for a mounted flag or be loaded with ssr: false via next/dynamic.

Documented limitations and gotchas:

  • The theme attribute is applied to the html element only; setting it on body or other elements is not supported.
  • In Next.js dev mode, the README says the page may still flash; flashing only disappears in production builds.
  • forcedTheme makes setTheme a no-op, so theme switching UI should be disabled when it is set.
  • Passing a custom themes array overrides the default ['light', 'dark'] list, so those need to be included explicitly if still wanted.