Una abstracción para temas en tu aplicación React.
appDir
useTheme
gancho temáticoConsulte el ejemplo en vivo para probarlo usted mismo.
$ npm install next-themes
# or
$ yarn add next-themes
Necesitará una App
personalizada para usar los siguientes temas. La _app
más simple se ve así:
// pages/_app.js
function MyApp ( { Component , pageProps } ) {
return < Component { ... pageProps } / >
}
export default MyApp
Agregar compatibilidad con el modo oscuro requiere 2 líneas de código:
// pages/_app.js
import { ThemeProvider } from 'next-themes'
function MyApp ( { Component , pageProps } ) {
return (
< ThemeProvider >
< Component { ... pageProps } / >
< / ThemeProvider >
)
}
export default MyApp
Deberá actualizar su app/layout.jsx
para usar los siguientes temas. El layout
más simple se ve así:
// app/layout.jsx
export default function Layout ( { children } ) {
return (
< html >
< head / >
< body > { children } < / body >
< / html >
)
}
Agregar compatibilidad con el modo oscuro requiere 2 líneas de código:
// app/layout.jsx
import { ThemeProvider } from 'next-themes'
export default function Layout ( { children } ) {
return (
< html suppressHydrationWarning >
< head / >
< body >
< ThemeProvider > { children } < / ThemeProvider >
< / body >
< / html >
)
}
Tenga en cuenta que ThemeProvider
es un componente de cliente, no un componente de servidor.
¡Nota! Si no agrega suprimirHydrationWarning a su
<html>
recibirá advertencias porquenext-themes
actualizan ese elemento. Esta propiedad solo se aplica a un nivel de profundidad, por lo que no bloqueará las advertencias de hidratación en otros elementos.
Eso es todo, su aplicación Next.js es totalmente compatible con el modo oscuro, incluida la preferencia del sistema con prefers-color-scheme
. El tema también se sincroniza inmediatamente entre pestañas. De forma predeterminada, next-themes modifica el atributo data-theme
en el elemento html
, que puedes usar fácilmente para diseñar tu aplicación:
: root {
/* Your default theme */
--background : white;
--foreground : black;
}
[ data-theme = 'dark' ] {
--background : black;
--foreground : white;
}
¡Nota! Si configura el atributo de su proveedor de temas en clase para Tailwind, los siguientes temas modificarán el atributo
class
en el elementohtml
. Ver con viento de cola.
Su interfaz de usuario necesitará conocer el tema actual y poder cambiarlo. El gancho useTheme
proporciona información sobre el tema:
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 >
)
}
¡Advertencia! El código anterior no es seguro para la hidratación y generará una advertencia de discrepancia de hidratación al renderizar con SSG o SSR. Esto se debe a que no podemos conocer el
theme
en el servidor, por lo que siempre estaráundefined
hasta que se monte en el cliente.Debe retrasar la representación de cualquier interfaz de usuario que alterne el tema hasta que se monte en el cliente. Vea el ejemplo.
Profundicemos en los detalles.
Toda la configuración de su tema se pasa a ThemeProvider.
storageKey = 'theme'
: clave utilizada para almacenar la configuración del tema en localStoragedefaultTheme = 'system'
: nombre del tema predeterminado (para v0.0.12 y versiones anteriores, el valor predeterminado era light
). Si enableSystem
es falso, el tema predeterminado es light
forcedTheme
: nombre de tema forzado para la página actual (no modifica la configuración del tema guardado)enableSystem = true
: si se debe cambiar entre dark
y light
según prefers-color-scheme
enableColorScheme = true
: si se debe indicar a los navegadores qué combinación de colores se utiliza (oscuro o claro) para la interfaz de usuario integrada, como entradas y botones.disableTransitionOnChange = false
: opcionalmente, deshabilite todas las transiciones CSS al cambiar de tema (ejemplo)themes = ['light', 'dark']
: Lista de nombres de temasattribute = 'data-theme'
: atributo HTML modificado según el tema activoclass
y data-*
(es decir, cualquier atributo de datos, data-mode
, data-color
, etc.) (ejemplo)value
: asignación opcional del nombre del tema al valor del atributoobject
donde clave es el nombre del tema y valor es el valor del atributo (ejemplo)nonce
: Nonce opcional pasado a la etiqueta de script
inyectada, que se utiliza para incluir en la lista de permitidos la secuencia de comandos de los siguientes temas en su CSP.scriptProps
: accesorios opcionales para pasar a la etiqueta script
inyectada (ejemplo)useTheme no toma parámetros, pero devuelve:
theme
: nombre del tema activosetTheme(name)
: Función para actualizar el tema. La API es idéntica a la función set devuelta por useState
-hook. Pase el nuevo valor del tema o use una devolución de llamada para configurar el nuevo tema según el tema actual.forcedTheme
: Tema de página forzado o falso. Si se configura forcedTheme
, debe deshabilitar cualquier interfaz de usuario para cambiar de tema.resolvedTheme
: si enableSystem
es verdadero y el tema activo es "sistema", esto indica si la preferencia del sistema se resolvió en "oscuro" o "claro". Por lo demás, idéntico al theme
systemTheme
: si enableSystem
es verdadero, representa la preferencia de tema del sistema ("oscuro" o "claro"), independientemente de cuál sea el tema activo.themes
: la lista de temas pasados a ThemeProvider
(con "sistema" agregado, si enableSystem
es verdadero)No está tan mal, ¿verdad? Veamos cómo utilizar estas propiedades con ejemplos:
El ejemplo en vivo muestra los siguientes temas en acción, con temas del sistema oscuros, claros y páginas con temas forzados.
Para versiones superiores a v0.0.12, el defaultTheme
se establece automáticamente en "sistema", por lo que para usar la preferencia del sistema simplemente puede usar:
< ThemeProvider >
Si no desea un tema del sistema, desactívelo mediante enableSystem
:
< ThemeProvider enableSystem = { false } >
Si su aplicación Next.js usa una clase para diseñar la página según el tema, cambie el atributo prop a class
:
< ThemeProvider attribute = "class" >
Ahora, configurar el tema en "oscuro" establecerá class="dark"
en el elemento html
.
Digamos que su nueva y genial página de marketing solo está en modo oscuro. La página siempre debe usar el tema oscuro y cambiar el tema no debería tener ningún efecto. Para forzar un tema en sus páginas Next.js, simplemente configure una variable en el componente de la página:
// pages/awesome-page.js
const Page = ( ) => { ... }
Page . theme = 'dark'
export default Page
En tu _app
, lee la variable y pásala a ThemeProvider:
function MyApp ( { Component , pageProps } ) {
return (
< ThemeProvider forcedTheme = { Component . theme || null } >
< Component { ... pageProps } / >
< / ThemeProvider >
)
}
¡Hecho! Su página siempre tiene un tema oscuro (independientemente de la preferencia del usuario) y llamar setTheme
desde useTheme
ahora no es operativo. Sin embargo, debes asegurarte de desactivar cualquier interfaz de usuario que normalmente cambiaría el tema:
const { forcedTheme } = useTheme ( )
// Theme is forced, we shouldn't allow user to change the theme
const disabled = ! ! forcedTheme
Escribí sobre esta técnica aquí. Podemos deshabilitar por la fuerza todas las transiciones CSS antes de cambiar el tema y volver a habilitarlas inmediatamente después. Esto garantiza que su interfaz de usuario con diferentes duraciones de transición no se sienta inconsistente al cambiar el tema.
Para habilitar este comportamiento, pase la propiedad disableTransitionOnChange
:
< ThemeProvider disableTransitionOnChange >
El nombre del tema activo se utiliza como valor de almacenamiento local y como valor del atributo DOM. Si el nombre del tema es "rosa", localStorage contendrá theme=pink
y el DOM será data-theme="pink"
. No puede modificar el valor de localStorage, pero puede modificar el valor de DOM.
Si queremos que el DOM represente data-theme="my-pink-theme"
cuando el tema sea "rosa", pase la propiedad value
:
< ThemeProvider value = { { pink : 'my-pink-theme' } } >
¡Hecho! Para ser más claro, esto afecta sólo al DOM. Así es como se verán todos los valores:
const { theme } = useTheme ( )
// => "pink"
localStorage . getItem ( 'theme' )
// => "pink"
document . documentElement . getAttribute ( 'data-theme' )
// => "my-pink-theme"
Rocket Loader es una optimización de Cloudflare que difiere la carga de scripts externos y en línea para priorizar el contenido del sitio web. Dado que los próximos temas se basan en una inyección de script para evitar que la pantalla parpadee al cargar la página, Rocket Loader rompe esta funcionalidad. Los scripts individuales se pueden ignorar agregando el atributo data-cfasync="false"
a la etiqueta del script:
< ThemeProvider scriptProps = { { 'data-cfasync' : 'false' } } >
¡next-themes está diseñado para admitir cualquier cantidad de temas! Simplemente pase una lista de temas:
< ThemeProvider themes = { [ 'pink' , 'red' , 'blue' ] } >
¡Nota! Cuando pasa
themes
, se anula el conjunto predeterminado de temas ("claro" y "oscuro"). Asegúrate de incluirlos si aún quieres tus temas claros y oscuros:
< ThemeProvider themes = { [ 'pink' , 'red' , 'blue' , 'light' , 'dark' ] } >
Para ver un ejemplo de cómo utilizar esto, consulte el ejemplo de varios temas.
Esta biblioteca no depende del estilo de su tema mediante variables CSS. Puedes codificar los valores en tu CSS y todo funcionará como se espera (sin ningún parpadeo):
html ,
body {
color : # 000 ;
background : # fff ;
}
[ data-theme = 'dark' ] ,
[ data-theme = 'dark' ] body {
color : # fff ;
background : # 000 ;
}
Next Themes es completamente independiente de CSS y funcionará con cualquier biblioteca. Por ejemplo, con Styled Components solo necesitas createGlobalStyle
en tu aplicación personalizada:
// 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 >
< / >
)
}
Debido a que no podemos conocer el theme
en el servidor, muchos de los valores devueltos por useTheme
no estarán undefined
hasta que se monten en el cliente. Esto significa que si intenta representar la interfaz de usuario según el tema actual antes de montarla en el cliente, verá un error de falta de coincidencia de hidratación.
El siguiente ejemplo de código no es seguro :
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
Para solucionar este problema, asegúrese de representar solo la interfaz de usuario que utiliza el tema actual cuando la página está montada en el cliente:
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
Alternativamente, puede cargar el componente de forma diferida en el lado del cliente. El siguiente ejemplo usa next/dynamic
pero también puedes usar React.lazy
:
import dynamic from 'next/dynamic'
const ThemeSwitch = dynamic ( ( ) => import ( './ThemeSwitch' ) , { ssr : false } )
const ThemePage = ( ) => {
return (
< div >
< ThemeSwitch / >
< / div >
)
}
export default ThemePage
Para evitar el cambio de diseño, considere renderizar un esqueleto/marcador de posición hasta que se monte en el lado del cliente.
Mostrar diferentes imágenes basadas en el tema actual también sufre el problema de falta de coincidencia de hidratación. Con next/image
puedes usar una imagen vacía hasta que se resuelva el tema:
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
También puedes usar CSS para ocultar o mostrar contenido según el tema actual. Para evitar la discrepancia en la hidratación, deberá representar ambas versiones de la interfaz de usuario y CSS ocultará la versión no utilizada. Por ejemplo:
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;
}
Visite el ejemplo en vivo • Vea el código fuente de ejemplo
¡NOTA! Tailwind solo admite el modo oscuro en la versión >2.
En su tailwind.config.js
, establezca la propiedad del modo oscuro en selector
:
// tailwind.config.js
module . exports = {
darkMode : 'selector'
}
Nota: Si está utilizando una versión anterior de tailwindcss <3.4.1, use 'class'
en lugar de 'selector'
Establece el atributo de tu proveedor de temas en clase:
// pages/_app.tsx
< ThemeProvider attribute = "class" >
Si está utilizando la propuesta de valor para especificar diferentes valores de atributos, asegúrese de que su tema oscuro use explícitamente el valor "oscuro", como lo requiere Tailwind.
¡Eso es todo! Ahora puedes usar clases específicas del modo oscuro:
< h1 className = "text-black dark:text-white" >
Tailwind también le permite usar un selector personalizado para el modo oscuro a partir de v3.4.1.
En ese caso, su tailwind.config.js
se vería así:
// tailwind.config.js
module . exports = {
// data-mode is used as an example, next-themes supports using any data attribute
darkMode : [ 'selector' , '[data-mode="dark"]' ]
…
}
Ahora configure el atributo de su ThemeProvider en data-mode
:
// pages/_app.tsx
< ThemeProvider attribute = "data-mode" >
Con esta configuración, ahora puedes usar las clases de modo oscuro de Tailwind, como en el ejemplo anterior:
ThemeProvider inyecta automáticamente un script en next/head
para actualizar el elemento html
con los atributos correctos antes de que se cargue el resto de la página. Esto significa que la página no parpadeará bajo ninguna circunstancia, incluidos temas forzados, temas del sistema, temas múltiples e incógnito. No se requiere noflash.js
.
¿Por qué mi página sigue parpadeando?
En el modo de desarrollo de Next.js, es posible que la página aún parpadee. Cuando cree su aplicación en modo de producción, no habrá flasheo.
¿Por qué aparece un error de discrepancia entre servidor y cliente?
Al usar useTheme
, verá un error de falta de coincidencia de hidratación al representar la interfaz de usuario que se basa en el tema actual. Esto se debe a que muchos de los valores devueltos por useTheme
no están definidos en el servidor, ya que no podemos leer localStorage
hasta que lo montemos en el cliente. Vea el ejemplo de cómo solucionar este error.
¿Necesito usar variables CSS con esta biblioteca?
No. Vea el ejemplo.
¿Puedo establecer el atributo de clase o datos en el cuerpo u otro elemento?
No. Si tiene una buena razón para admitir esta función, abra un problema.
¿Puedo usar este paquete con Gatsby o CRA?
Sí, a partir de la versión 0.3.0.
¿Se minimiza el script inyectado?
Sí.
¿Por qué es necesario resolvedTheme
?
Al admitir la preferencia de tema del sistema, desea asegurarse de que se refleje en su interfaz de usuario. Esto significa que sus botones, selecciones, menús desplegables o cualquier cosa que use para indicar el tema actual deben decir "Sistema" cuando la preferencia de tema del Sistema esté activa.
Si no distinguiéramos entre theme
y resolvedTheme
, la interfaz de usuario mostraría "Oscuro" o "Claro", cuando en realidad debería ser "Sistema".
resolvedTheme
es útil para modificar comportamientos o estilos en tiempo de ejecución:
const { resolvedTheme } = useTheme ( )
< div style = { { color : resolvedTheme === 'dark' ? 'white' : 'black' } } >
Si no hubiéramos resolvedTheme
y solo usáramos theme
, perdería información sobre el estado de su interfaz de usuario (solo sabría que el tema es "sistema" y no lo que se resolvió).