MCPcopy
hub / github.com/pacocoursey/next-themes

github.com/pacocoursey/next-themes @v0.4.6 sqlite

repository ↗ · DeepWiki ↗ · release v0.4.6 ↗
41 symbols 113 edges 32 files 0 documented · 0%
README

next-themes next-themes minzip package size Version

An abstraction for themes in your React app.

  • ✅ Perfect dark mode in 2 lines of code
  • ✅ System setting with prefers-color-scheme
  • ✅ Themed browser UI with color-scheme
  • ✅ Support for Next.js 13 appDir
  • ✅ No flash on load (both SSR and SSG)
  • ✅ Sync theme across tabs and windows
  • ✅ Disable flashing when changing themes
  • ✅ Force pages to specific themes
  • ✅ Class or data attribute selector
  • useTheme hook

Check out the Live Example to try it for yourself.

Install

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

Use

With pages/

You'll need a Custom App to use next-themes. The simplest _app looks like this:

// pages/_app.js

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

export default MyApp

Adding dark mode support takes 2 lines of code:

// pages/_app.js
import { ThemeProvider } from 'next-themes'

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

export default MyApp

With app/

You'll need to update your app/layout.jsx to use next-themes. The simplest layout looks like this:

// app/layout.jsx
export default function Layout({ children }) {
  return (
    <html>
      <head />
      <body>{children}</body>
    </html>
  )
}

Adding dark mode support takes 2 lines of code:

// app/layout.jsx
import { ThemeProvider } from 'next-themes'

export default function Layout({ children }) {
  return (
    <html suppressHydrationWarning>
      <head />
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  )
}

Note that ThemeProvider is a client component, not a server component.

Note! If you do not add suppressHydrationWarning to your <html> you will get warnings because next-themes updates that element. This property only applies one level deep, so it won't block hydration warnings on other elements.

HTML & CSS

That's it, your Next.js app fully supports dark mode, including System preference with prefers-color-scheme. The theme is also immediately synced between tabs. By default, next-themes modifies the data-theme attribute on the html element, which you can easily use to style your app:

:root {
  /* Your default theme */
  --background: white;
  --foreground: black;
}

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

Note! If you set the attribute of your Theme Provider to class for Tailwind next-themes will modify the class attribute on the html element. See With TailwindCSS.

useTheme

Your UI will need to know the current theme and be able to change it. The useTheme hook provides theme information:

import { useTheme } from 'next-themes'

const ThemeChanger = () => {
  const { theme, setTheme } = useTheme()

  return (



      The current theme is: {theme}
      <button onClick={() => setTheme('light')}>Light Mode</button>
      <button onClick={() => setTheme('dark')}>Dark Mode</button>



  )
}

Warning! The above code is hydration unsafe and will throw a hydration mismatch warning when rendering with SSG or SSR. This is because we cannot know the theme on the server, so it will always be undefined until mounted on the client.

You should delay rendering any theme toggling UI until mounted on the client. See the example.

API

Let's dig into the details.

ThemeProvider

All your theme configuration is passed to ThemeProvider.

  • storageKey = 'theme': Key used to store theme setting in localStorage
  • defaultTheme = 'system': Default theme name (for v0.0.12 and lower the default was light). If enableSystem is false, the default theme is light
  • forcedTheme: Forced theme name for the current page (does not modify saved theme settings)
  • enableSystem = true: Whether to switch between dark and light based on prefers-color-scheme
  • enableColorScheme = true: Whether to indicate to browsers which color scheme is used (dark or light) for built-in UI like inputs and buttons
  • disableTransitionOnChange = false: Optionally disable all CSS transitions when switching themes (example)
  • themes = ['light', 'dark']: List of theme names
  • attribute = 'data-theme': HTML attribute modified based on the active theme
  • accepts class and data-* (meaning any data attribute, data-mode, data-color, etc.) (example)
  • value: Optional mapping of theme name to attribute value
  • value is an object where key is the theme name and value is the attribute value (example)
  • nonce: Optional nonce passed to the injected script tag, used to allow-list the next-themes script in your CSP
  • scriptProps: Optional props to pass to the injected script tag (example)

useTheme

useTheme takes no parameters, but returns:

  • theme: Active theme name
  • setTheme(name): Function to update the theme. The API is identical to the set function returned by useState-hook. Pass the new theme value or use a callback to set the new theme based on the current theme.
  • forcedTheme: Forced page theme or falsy. If forcedTheme is set, you should disable any theme switching UI
  • resolvedTheme: If enableSystem is true and the active theme is "system", this returns whether the system preference resolved to "dark" or "light". Otherwise, identical to theme
  • systemTheme: If enableSystem is true, represents the System theme preference ("dark" or "light"), regardless what the active theme is
  • themes: The list of themes passed to ThemeProvider (with "system" appended, if enableSystem is true)

Not too bad, right? Let's see how to use these properties with examples:

Examples

The Live Example shows next-themes in action, with dark, light, system themes and pages with forced themes.

Use System preference by default

For versions above v0.0.12, the defaultTheme is automatically set to "system", so to use System preference you can simply use:

<ThemeProvider>

Ignore System preference

If you don't want a System theme, disable it via enableSystem:

<ThemeProvider enableSystem={false}>

Class instead of data attribute

If your Next.js app uses a class to style the page based on the theme, change the attribute prop to class:

<ThemeProvider attribute="class">

Now, setting the theme to "dark" will set class="dark" on the html element.

Force page to a theme

Let's say your cool new marketing page is dark mode only. The page should always use the dark theme, and changing the theme should have no effect. To force a theme on your Next.js pages, simply set a variable on the page component:

// pages/awesome-page.js

const Page = () => { ... }
Page.theme = 'dark'
export default Page

In your _app, read the variable and pass it to ThemeProvider:

function MyApp({ Component, pageProps }) {
  return (
    <ThemeProvider forcedTheme={Component.theme || null}>
      <Component {...pageProps} />
    </ThemeProvider>
  )
}

Done! Your page is always dark theme (regardless of user preference), and calling setTheme from useTheme is now a no-op. However, you should make sure to disable any of your UI that would normally change the theme:

const { forcedTheme } = useTheme()

// Theme is forced, we shouldn't allow user to change the theme
const disabled = !!forcedTheme

Disable transitions on theme change

I wrote about this technique here. We can forcefully disable all CSS transitions before the theme is changed, and re-enable them immediately afterwards. This ensures your UI with different transition durations won't feel inconsistent when changing the theme.

To enable this behavior, pass the disableTransitionOnChange prop:

<ThemeProvider disableTransitionOnChange>

Differing DOM attribute and theme name

The name of the active theme is used as both the localStorage value and the value of the DOM attribute. If the theme name is "pink", localStorage will contain theme=pink and the DOM will be data-theme="pink". You cannot modify the localStorage value, but you can modify the DOM value.

If we want the DOM to instead render data-theme="my-pink-theme" when the theme is "pink", pass the value prop:

<ThemeProvider value={{ pink: 'my-pink-theme' }}>

Done! To be extra clear, this affects only the DOM. Here's how all the values will look:

const { theme } = useTheme()
// => "pink"

localStorage.getItem('theme')
// => "pink"

document.documentElement.getAttribute('data-theme')
// => "my-pink-theme"

Using with Cloudflare Rocket Loader

Rocket Loader is a Cloudflare optimization that defers the loading of inline and external scripts to prioritize the website content. Since next-themes relies on a script injection to avoid screen flashing on page load, Rocket Loader breaks this functionality. Individual scripts can be ignored by adding the data-cfasync="false" attribute to the script tag:

<ThemeProvider scriptProps={{ 'data-cfasync': 'false' }}>

More than light and dark mode

next-themes is designed to support any number of themes! Simply pass a list of themes:

<ThemeProvider themes={['pink', 'red', 'blue']}>

Note! When you pass themes, the default set of themes ("light" and "dark") are overridden. Make sure you include those if you still want your light and dark themes:

<ThemeProvider themes={['pink', 'red', 'blue', 'light', 'dark']}>

For an example on how to use this, check out the multi-theme example

Without CSS variables

This library does not rely on your theme styling using CSS variables. You can hard-code the values in your CSS, and everything will work as expected (without any flashing):

html,
body {
  color: #000;
  background: #fff;
}

[data-theme='dark'],
[data-theme='dark'] body {
  color: #fff;
  background: #000;
}

With Styled Components and any CSS-in-JS

Next Themes is completely CSS independent, it will work with any library. For example, with Styled Components you just need to createGlobalStyle in your custom App:

// pages/_app.js
import { createGlobalStyle } from 'styled-components'
import { ThemeProvider } from 'next-themes'

// Your themeing variables
const GlobalStyle = createGlobalStyle`
  :root {
    --fg: #000;
    --bg: #fff;
  }

  [data-theme="dark"] {
    --fg: #fff;
    --bg: #000;
  }
`

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

Avoid Hydration Mismatch

Because we cannot know the theme on the server, many of the values returned from useTheme will be undefined until mounted on the client. This means if you try to render UI based on the current theme before mounting on the client, you will see a hydration mismatch error.

The following code sample is unsafe:

import { useTheme } from 'next-themes'

// Do NOT use this! It will throw a hydration mismatch error.
const ThemeSwitch = () => {
  const { theme, setTheme } = useTheme()

  return (
    <select value={theme} onChange={e => setTheme(e.target.value)}>
      <option value="system">System</option>
      <option value="dark">Dark</option>
      <option value="light">Light</option>
    </select>
  )
}

export default ThemeSwitch

To fix this, make sure you only render UI that uses the current theme when the page is mounted on the client:

import { useState, useEffect } from 'react'
import { useTheme } from 'next-themes'

const ThemeSwitch = () => {
  const [mounted, setMounted] = useState(false)
  const { theme, setTheme } = useTheme()

  // useEffect only runs on the client, so now we can safely show the UI
  useEffect(() => {
    setMounted(true)
  }, [])

  if (!mounted) {
    return null
  }

  return (
    <select value={theme} onChange={e => setTheme(e.target.value)}>
      <option value="system">System</option>
      <option value="dark">Dark</option>
      <option value="light">Light</option>
    </select>
  )
}

export default ThemeSwitch

Alternatively, you could lazy load the component on the client side. The following example uses next/dynamic but you could also use React.lazy:

import dynamic from 'next/dynamic'

const ThemeSwitch = dynamic(() => import('./ThemeSwitch'), { ssr: false })

const ThemePage = () => {
  return (



      <ThemeSwitch />



  )
}

export default ThemePage

To avoid Layout Shift, consider rendering a skeleton/placeholder until mounted on the client side.

Images

Showing different images based on the current theme also suffers from the hydration mismatch problem. With next/image you can use an empty image until the theme is resolved:

```jsx im

Extension points exported contracts — how you extend this code

ValueObject (Interface)
(no doc)
next-themes/src/types.ts
ScriptProps (Interface)
(no doc)
next-themes/src/types.ts
UseThemeProps (Interface)
(no doc)
next-themes/src/types.ts
ThemeProviderProps (Interface)
(no doc)
next-themes/src/types.ts

Core symbols most depended-on inside this repo

useTheme
called by 18
next-themes/src/index.tsx
checkAppliedTheme
called by 14
test/util.ts
makeBrowserContext
called by 6
test/util.ts
getSystemTheme
called by 3
next-themes/src/index.tsx
checkStoredTheme
called by 2
test/util.ts
updateDOM
called by 2
next-themes/src/script.ts
setColorScheme
called by 1
next-themes/src/script.ts
getSystemTheme
called by 1
next-themes/src/script.ts

Shape

Function 34
Interface 4
Class 2
Method 1

Languages

TypeScript100%

Modules by API surface

next-themes/src/index.tsx8 symbols
next-themes/src/types.ts4 symbols
next-themes/src/script.ts4 symbols
test/util.ts3 symbols
next-themes/__tests__/index.test.tsx3 symbols
examples/tailwind/pages/_document.js3 symbols
test/switch-theme.test.ts2 symbols
test/system-theme.test.ts1 symbols
test/forced-theme.test.ts1 symbols
examples/with-app-dir/src/app/page.tsx1 symbols
examples/with-app-dir/src/app/layout.tsx1 symbols
examples/with-app-dir/src/app/ThemeToggle.tsx1 symbols

Dependencies from manifests, versioned

@playwright/test1.51.0 · 1×
@testing-library/react14.2.1 · 1×
@types/node20.5.7 · 1×
@types/react18.2.65 · 1×
@types/react-dom18.2.7 · 1×
autoprefixer10.4.15 · 1×
jsdom24.0.0 · 1×
next14.2.21 · 1×
next-themesworkspace:* · 1×
postcss8.4.31 · 1×
prettier3.2.5 · 1×
react18.2.0 · 1×

For agents

$ claude mcp add next-themes \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact