Security News
JSR Working Group Kicks Off with Ambitious Roadmap and Plans for Open Governance
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
next-themes
Advanced tools
The next-themes package is a utility for managing themes in Next.js applications. It provides a simple and efficient way to implement dark mode, light mode, and custom themes with minimal configuration.
Theme Provider
The ThemeProvider component wraps your application and provides the context for theme management. The `attribute` prop specifies how the theme is applied, such as using a class or data attribute.
```jsx
import { ThemeProvider } from 'next-themes';
function MyApp({ Component, pageProps }) {
return (
<ThemeProvider attribute="class">
<Component {...pageProps} />
</ThemeProvider>
);
}
export default MyApp;
```
Use Theme Hook
The `useTheme` hook allows you to access and manipulate the current theme. You can use it to create theme switchers or to apply theme-specific logic in your components.
```jsx
import { useTheme } from 'next-themes';
function ThemeSwitcher() {
const { theme, setTheme } = useTheme();
return (
<div>
<button onClick={() => setTheme('light')}>Light Mode</button>
<button onClick={() => setTheme('dark')}>Dark Mode</button>
<button onClick={() => setTheme('system')}>System Default</button>
</div>
);
}
```
Custom Themes
You can define custom themes and pass them to the ThemeProvider. This allows you to have more control over the appearance of your application and to create unique themes beyond just light and dark modes.
```jsx
import { ThemeProvider } from 'next-themes';
const customThemes = {
light: {
background: '#ffffff',
color: '#000000'
},
dark: {
background: '#000000',
color: '#ffffff'
}
};
function MyApp({ Component, pageProps }) {
return (
<ThemeProvider themes={customThemes} attribute="class">
<Component {...pageProps} />
</ThemeProvider>
);
}
export default MyApp;
```
Styled-components is a library for React and React Native that allows you to use component-level styles in your application. It supports theming through its ThemeProvider, but it is more focused on styling components rather than managing global themes.
Emotion is a library designed for writing CSS styles with JavaScript. It provides powerful and flexible theming capabilities through its ThemeProvider and useTheme hook, similar to next-themes, but it is also more focused on styling components.
Tailwind CSS is a utility-first CSS framework that can be used to create custom themes. While it does not provide a built-in theme management system like next-themes, it allows you to define custom themes using configuration files and utility classes.
An abstraction for themes in your Next.js app.
useTheme
hookCheck out the Live Example to try it for yourself.
$ npm install next-themes
# or
$ yarn add next-themes
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:
import { ThemeProvider } from 'next-themes'
function MyApp({ Component, pageProps }) {
return (
<ThemeProvider>
<Component {...pageProps} />
</ThemeProvider>
)
}
export default MyApp
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;
}
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 (
<div>
The current theme is: {theme}
<button onClick={() => setTheme('light')}>Light Mode</button>
<button onClick={() => setTheme('dark')}>Dark Mode</button>
</div>
)
}
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 beundefined
until mounted on the client.You should delay rendering any theme toggling UI until mounted on the client. See the example.
Let's dig into the details.
All your theme configuration is passed to ThemeProvider.
storageKey = 'theme'
: Key used to store theme setting in localStoragedefaultTheme = '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 buttonsdisableTransitionOnChange = false
: Optionally disable all CSS transitions when switching themes (example)themes = ['light', 'dark']
: List of theme namesattribute = 'data-theme'
: HTML attribute modified based on the active theme
class
and data-*
(meaning any data attribute, data-mode
, data-color
, etc.) (example)value
: Optional mapping of theme name to attribute value
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 CSPuseTheme takes no parameters, but returns:
theme
: Active theme namesetTheme(name)
: Function to update the themeforcedTheme
: Forced page theme or falsy. If forcedTheme
is set, you should disable any theme switching UIresolvedTheme
: Returns the effective theme color of the page.
enableSystem
is true and the active theme is "system", this returns whether the system preference resolved to "dark" or "light".forcedTheme
is set, the name of the forced theme is returned.theme
.systemTheme
: If enableSystem
is true, represents the System theme preference ("dark" or "light"), regardless what the active theme isthemes
: 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:
The Live Example shows next-themes in action, with dark, light, system themes and pages with forced themes.
The defaultTheme
is "light". If you want to respect the System preference instead, set it to "system":
<ThemeProvider defaultTheme="system">
If you don't want a System theme, disable it via enableSystem
:
<ThemeProvider enableSystem={false}>
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.
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
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>
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"
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']}>
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;
}
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>
</>
)
}
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 { resolvedTheme, setTheme } = useTheme()
return (
<select value={resolvedTheme} 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 { resolvedTheme, 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={resolvedTheme} 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 avoid Layout Shift, consider rendering a skeleton/placeholder until mounted on the client side.
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:
import Image from 'next/image'
import { useTheme } from 'next-themes'
function ThemedImage() {
const { resolvedTheme } = useTheme()
let src
switch (resolvedTheme) {
case 'light':
src = '/light.png'
break
case 'dark':
src = '/dark.png'
break
default:
src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
break
}
return <Image src={src} width={400} height={400} />
}
export default ThemedImage
You can also use CSS to hide or show content based on the current theme. To avoid the hydration mismatch, you'll need to render both versions of the UI, with CSS hiding the unused version. For example:
function ThemedImage() {
return (
<>
{/* When the theme is dark, hide this div */}
<div data-hide-on-theme="dark">
<Image src="light.png" width={400} height={400} />
</div>
{/* When the theme is light, hide this div */}
<div data-hide-on-theme="light">
<Image src="dark.png" width={400} height={400} />
</div>
</>
)
}
export default ThemedImage
[data-theme='dark'] [data-hide-on-theme='dark'],
[data-theme='light'] [data-hide-on-theme='light'] {
display: none;
}
Visit the live example • View the example source code
NOTE! Tailwind only supports dark mode in version >2.
In your tailwind.config.js
, set the dark mode property to class:
// tailwind.config.js
module.exports = {
darkMode: 'class'
}
Set the attribute for your Theme Provider to class:
// pages/_app.js
<ThemeProvider attribute="class">
If you're using the value
prop to specify different attribute values, make sure your dark theme explicitly uses the "dark" value, as required by Tailwind.
That's it! Now you can use dark-mode specific classes:
<h1 className="text-black dark:text-white">
ThemeProvider automatically injects a script into next/head
to update the html
element with the correct attributes before the rest of your page loads. This means the page will not flash under any circumstances, including forced themes, system theme, multiple themes, and incognito. No noflash.js
required.
Why is my page still flashing?
In Next.js dev mode, the page may still flash. When you build your app in production mode, there will be no flashing.
Why do I get server/client mismatch error?
When using useTheme
, you will use see a hydration mismatch error when rendering UI that relies on the current theme. This is because many of the values returned by useTheme
are undefined on the server, since we can't read localStorage
until mounting on the client. See the example for how to fix this error.
Do I need to use CSS variables with this library?
Nope. See the example.
Can I set the class or data attribute on the body or another element?
Nope. If you have a good reason for supporting this feature, please open an issue.
Can I use this package with Gatsby or CRA?
Nope.
Is the injected script minified?
Yes, using Terser.
Why is resolvedTheme
necessary?
When supporting the System theme preference, you want to make sure that's reflected in your UI. This means your buttons, selects, dropdowns, or whatever you use to indicate the current theme should say "System" when the System theme preference is active.
If we didn't distinguish between theme
and resolvedTheme
, the UI would show "Dark" or "Light", when it should really be "System".
resolvedTheme
is then useful for modifying behavior or styles at runtime:
const { resolvedTheme } = useTheme()
<div style={{ color: resolvedTheme === 'dark' ? white : black }}>
If we didn't have resolvedTheme
and only used theme
, you'd lose information about the state of your UI (you would only know the theme is "system", and not what it resolved to).
FAQs
An abstraction for themes in your React app.
The npm package next-themes receives a total of 706,361 weekly downloads. As such, next-themes popularity was classified as popular.
We found that next-themes demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
Security News
Research
An advanced npm supply chain attack is leveraging Ethereum smart contracts for decentralized, persistent malware control, evading traditional defenses.
Security News
Research
Attackers are impersonating Sindre Sorhus on npm with a fake 'chalk-node' package containing a malicious backdoor to compromise developers' projects.