Une abstraction pour les thèmes dans votre application React.
appDir
useTheme
le crochet thématiqueConsultez l'exemple en direct pour l'essayer par vous-même.
$ npm install next-themes
# or
$ yarn add next-themes
Vous aurez besoin d'une App
personnalisée pour utiliser les thèmes suivants. La _app
la plus simple ressemble à ceci :
// pages/_app.js
function MyApp ( { Component , pageProps } ) {
return < Component { ... pageProps } / >
}
export default MyApp
L'ajout de la prise en charge du mode sombre nécessite 2 lignes de code :
// pages/_app.js
import { ThemeProvider } from 'next-themes'
function MyApp ( { Component , pageProps } ) {
return (
< ThemeProvider >
< Component { ... pageProps } / >
< / ThemeProvider >
)
}
export default MyApp
Vous devrez mettre à jour votre app/layout.jsx
pour utiliser les thèmes suivants. La layout
la plus simple ressemble à ceci :
// app/layout.jsx
export default function Layout ( { children } ) {
return (
< html >
< head / >
< body > { children } < / body >
< / html >
)
}
L'ajout de la prise en charge du mode sombre nécessite 2 lignes de code :
// app/layout.jsx
import { ThemeProvider } from 'next-themes'
export default function Layout ( { children } ) {
return (
< html suppressHydrationWarning >
< head / >
< body >
< ThemeProvider > { children } < / ThemeProvider >
< / body >
< / html >
)
}
Notez que ThemeProvider
est un composant client et non un composant serveur.
Note! Si vous n'ajoutez pas suppressHydrationWarning à votre
<html>
vous recevrez des avertissements carnext-themes
met à jour cet élément. Cette propriété ne s'applique qu'à un seul niveau de profondeur, elle ne bloquera donc pas les avertissements d'hydratation sur les autres éléments.
Voilà, votre application Next.js prend entièrement en charge le mode sombre, y compris les préférences système avec prefers-color-scheme
. Le thème est également immédiatement synchronisé entre les onglets. Par défaut, next-themes modifie l'attribut data-theme
sur l'élément html
, que vous pouvez facilement utiliser pour styliser votre application :
: root {
/* Your default theme */
--background : white;
--foreground : black;
}
[ data-theme = 'dark' ] {
--background : black;
--foreground : white;
}
Note! Si vous définissez l'attribut de votre fournisseur de thème sur class pour Tailwind next-themes modifiera l'attribut
class
sur l'élémenthtml
. Voir avec le vent arrière.
Votre interface utilisateur devra connaître le thème actuel et pouvoir le modifier. Le hook useTheme
fournit des informations sur le thème :
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 >
)
}
Avertissement! Le code ci-dessus est dangereux en matière d'hydratation et générera un avertissement de non-concordance d'hydratation lors du rendu avec SSG ou SSR. En effet, nous ne pouvons pas connaître le
theme
sur le serveur, il sera donc toujoursundefined
jusqu'à ce qu'il soit monté sur le client.Vous devez retarder le rendu de tout thème basculant l'interface utilisateur jusqu'à ce qu'il soit monté sur le client. Voir l'exemple.
Entrons dans les détails.
Toute la configuration de votre thème est transmise à ThemeProvider.
storageKey = 'theme'
: Clé utilisée pour stocker les paramètres du thème dans localStoragedefaultTheme = 'system'
: Nom du thème par défaut (pour la version 0.0.12 et inférieure, la valeur par défaut était light
). Si enableSystem
est faux, le thème par défaut est light
forcedTheme
: Nom du thème forcé pour la page actuelle (ne modifie pas les paramètres du thème enregistré)enableSystem = true
: s'il faut basculer entre dark
et light
en fonction prefers-color-scheme
enableColorScheme = true
: s'il faut indiquer aux navigateurs quel jeu de couleurs est utilisé (sombre ou clair) pour l'interface utilisateur intégrée comme les entrées et les boutonsdisableTransitionOnChange = false
: Désactivez éventuellement toutes les transitions CSS lors du changement de thème (exemple)themes = ['light', 'dark']
: Liste des noms de thèmesattribute = 'data-theme'
: attribut HTML modifié en fonction du thème actifclass
et data-*
(c'est-à-dire tout attribut de données, data-mode
, data-color
, etc.) (exemple)value
: Mappage facultatif du nom du thème à la valeur de l'attributobject
où key est le nom du thème et value est la valeur de l'attribut (exemple)nonce
: nonce facultatif transmis à la balise script
injectée, utilisée pour autoriser le script des thèmes suivants dans votre CSPscriptProps
: accessoires facultatifs à transmettre à la balise script
injectée (exemple)useTheme ne prend aucun paramètre, mais renvoie :
theme
: Nom du thème actifsetTheme(name)
: Fonction pour mettre à jour le thème. L'API est identique à la fonction set renvoyée par useState
-hook. Transmettez la nouvelle valeur du thème ou utilisez un rappel pour définir le nouveau thème en fonction du thème actuel.forcedTheme
: Thème de page forcé ou faux. Si forcedTheme
est défini, vous devez désactiver toute interface utilisateur de changement de thèmeresolvedTheme
: Si enableSystem
est vrai et que le thème actif est "système", cela indique si la préférence système est résolue en "sombre" ou "clair". Sinon identique au theme
systemTheme
: Si enableSystem
est vrai, représente la préférence de thème système ("sombre" ou "clair"), quel que soit le thème actif.themes
: La liste des thèmes transmis à ThemeProvider
(avec "system" ajouté, si enableSystem
est vrai)Pas trop mal, non ? Voyons comment utiliser ces propriétés avec des exemples :
L'exemple en direct montre les thèmes suivants en action, avec des thèmes sombres, clairs, système et des pages avec des thèmes forcés.
Pour les versions supérieures à la v0.0.12, le defaultTheme
est automatiquement défini sur "système", donc pour utiliser les préférences système, vous pouvez simplement utiliser :
< ThemeProvider >
Si vous ne voulez pas de thème Système, désactivez-le via enableSystem
:
< ThemeProvider enableSystem = { false } >
Si votre application Next.js utilise une classe pour styliser la page en fonction du thème, remplacez l'attribut prop par class
:
< ThemeProvider attribute = "class" >
Maintenant, définir le thème sur "dark" définira class="dark"
sur l'élément html
.
Disons que votre nouvelle page marketing sympa est uniquement en mode sombre. La page doit toujours utiliser le thème sombre et la modification du thème ne devrait avoir aucun effet. Pour forcer un thème sur vos pages Next.js, définissez simplement une variable sur le composant page :
// pages/awesome-page.js
const Page = ( ) => { ... }
Page . theme = 'dark'
export default Page
Dans votre _app
, lisez la variable et transmettez-la à ThemeProvider :
function MyApp ( { Component , pageProps } ) {
return (
< ThemeProvider forcedTheme = { Component . theme || null } >
< Component { ... pageProps } / >
< / ThemeProvider >
)
}
Fait! Votre page est toujours un thème sombre (quelles que soient les préférences de l'utilisateur), et appeler setTheme
depuis useTheme
est désormais impossible. Cependant, vous devez vous assurer de désactiver toute interface utilisateur qui modifierait normalement le thème :
const { forcedTheme } = useTheme ( )
// Theme is forced, we shouldn't allow user to change the theme
const disabled = ! ! forcedTheme
J'ai écrit sur cette technique ici. Nous pouvons désactiver de force toutes les transitions CSS avant que le thème ne soit modifié, et les réactiver immédiatement après. Cela garantit que votre interface utilisateur avec différentes durées de transition ne semblera pas incohérente lors du changement de thème.
Pour activer ce comportement, transmettez la prop disableTransitionOnChange
:
< ThemeProvider disableTransitionOnChange >
Le nom du thème actif est utilisé à la fois comme valeur localStorage et comme valeur de l'attribut DOM. Si le nom du thème est "pink", localStorage contiendra theme=pink
et le DOM sera data-theme="pink"
. Vous ne pouvez pas modifier la valeur localStorage, mais vous pouvez modifier la valeur DOM.
Si nous voulons que le DOM restitue à la place data-theme="my-pink-theme"
lorsque le thème est "rose", transmettez la value
prop :
< ThemeProvider value = { { pink : 'my-pink-theme' } } >
Fait! Pour être plus clair, cela affecte uniquement le DOM. Voici à quoi ressembleront toutes les valeurs :
const { theme } = useTheme ( )
// => "pink"
localStorage . getItem ( 'theme' )
// => "pink"
document . documentElement . getAttribute ( 'data-theme' )
// => "my-pink-theme"
Rocket Loader est une optimisation Cloudflare qui diffère le chargement des scripts en ligne et externes pour hiérarchiser le contenu du site Web. Étant donné que next-themes repose sur une injection de script pour éviter le clignotement de l'écran lors du chargement de la page, Rocket Loader interrompt cette fonctionnalité. Les scripts individuels peuvent être ignorés en ajoutant l'attribut data-cfasync="false"
à la balise script :
< ThemeProvider scriptProps = { { 'data-cfasync' : 'false' } } >
next-themes est conçu pour prendre en charge un certain nombre de thèmes ! Passez simplement une liste de thèmes :
< ThemeProvider themes = { [ 'pink' , 'red' , 'blue' ] } >
Note! Lorsque vous transmettez
themes
, l'ensemble de thèmes par défaut ("clair" et "sombre") est remplacé. Assurez-vous de les inclure si vous souhaitez toujours vos thèmes clairs et sombres :
< ThemeProvider themes = { [ 'pink' , 'red' , 'blue' , 'light' , 'dark' ] } >
Pour un exemple sur la façon d'utiliser ceci, consultez l'exemple multi-thème
Cette bibliothèque ne repose pas sur le style de votre thème à l'aide de variables CSS. Vous pouvez coder en dur les valeurs dans votre CSS, et tout fonctionnera comme prévu (sans aucun flash) :
html ,
body {
color : # 000 ;
background : # fff ;
}
[ data-theme = 'dark' ] ,
[ data-theme = 'dark' ] body {
color : # fff ;
background : # 000 ;
}
Next Themes est complètement indépendant de CSS, il fonctionnera avec n’importe quelle bibliothèque. Par exemple, avec les composants stylés, il vous suffit de createGlobalStyle
dans votre application personnalisée :
// 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 >
< / >
)
}
Comme nous ne pouvons pas connaître le theme
sur le serveur, la plupart des valeurs renvoyées par useTheme
ne seront undefined
tant qu'elles ne seront pas montées sur le client. Cela signifie que si vous essayez de restituer l'interface utilisateur en fonction du thème actuel avant de monter sur le client, vous verrez une erreur de non-concordance d'hydratation.
L'exemple de code suivant n'est pas sécurisé :
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
Pour résoudre ce problème, assurez-vous de restituer uniquement l'interface utilisateur qui utilise le thème actuel lorsque la page est montée sur le 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
Alternativement, vous pouvez charger paresseux le composant côté client. L'exemple suivant utilise next/dynamic
mais vous pouvez également utiliser React.lazy
:
import dynamic from 'next/dynamic'
const ThemeSwitch = dynamic ( ( ) => import ( './ThemeSwitch' ) , { ssr : false } )
const ThemePage = ( ) => {
return (
< div >
< ThemeSwitch / >
< / div >
)
}
export default ThemePage
Pour éviter le décalage de mise en page, envisagez de restituer un squelette/espace réservé jusqu'à ce qu'il soit monté côté client.
L'affichage de différentes images basées sur le thème actuel souffre également du problème d'inadéquation de l'hydratation. Avec next/image
vous pouvez utiliser une image vide jusqu'à ce que le thème soit résolu :
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
Vous pouvez également utiliser CSS pour masquer ou afficher du contenu en fonction du thème actuel. Pour éviter l'inadéquation de l'hydratation, vous devrez restituer les deux versions de l'interface utilisateur, CSS masquant la version inutilisée. Par exemple:
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;
}
Visitez l'exemple en direct • Consultez l'exemple de code source
NOTE! Tailwind ne prend en charge le mode sombre que dans la version >2.
Dans votre tailwind.config.js
, définissez la propriété dark mode sur selector
:
// tailwind.config.js
module . exports = {
darkMode : 'selector'
}
Remarque : si vous utilisez une ancienne version de tailwindcss < 3.4.1, utilisez 'class'
au lieu de 'selector'
Définissez l'attribut de votre fournisseur de thème sur class :
// pages/_app.tsx
< ThemeProvider attribute = "class" >
Si vous utilisez l'accessoire value pour spécifier différentes valeurs d'attribut, assurez-vous que votre thème sombre utilise explicitement la valeur "dark", comme l'exige Tailwind.
C'est ça! Vous pouvez désormais utiliser des classes spécifiques au mode sombre :
< h1 className = "text-black dark:text-white" >
Tailwind vous permet également d'utiliser un sélecteur personnalisé pour le mode sombre à partir de la version 3.4.1.
Dans ce cas, votre tailwind.config.js
ressemblerait à ceci :
// tailwind.config.js
module . exports = {
// data-mode is used as an example, next-themes supports using any data attribute
darkMode : [ 'selector' , '[data-mode="dark"]' ]
…
}
Définissez maintenant l'attribut de votre ThemeProvider sur data-mode
:
// pages/_app.tsx
< ThemeProvider attribute = "data-mode" >
Avec cette configuration, vous pouvez désormais utiliser les classes du mode sombre de Tailwind, comme dans l'exemple précédent :
ThemeProvider injecte automatiquement un script dans next/head
pour mettre à jour l'élément html
avec les attributs corrects avant le chargement du reste de votre page. Cela signifie que la page ne clignotera en aucun cas, y compris les thèmes forcés, le thème système, les thèmes multiples et la navigation privée. Aucun noflash.js
requis.
Pourquoi ma page clignote-t-elle toujours ?
En mode développement Next.js, la page peut encore clignoter. Lorsque vous créez votre application en mode production, il n'y aura pas de clignotement.
Pourquoi est-ce que j'obtiens une erreur d'incompatibilité serveur/client ?
Lorsque vous utilisez useTheme
, vous verrez une erreur d’incompatibilité d’hydratation lors du rendu de l’interface utilisateur qui repose sur le thème actuel. En effet, la plupart des valeurs renvoyées par useTheme
ne sont pas définies sur le serveur, puisque nous ne pouvons pas lire localStorage
avant le montage sur le client. Voir l'exemple pour savoir comment corriger cette erreur.
Dois-je utiliser des variables CSS avec cette bibliothèque ?
Non. Voir l'exemple.
Puis-je définir l'attribut class ou data sur le corps ou un autre élément ?
Non. Si vous avez une bonne raison de prendre en charge cette fonctionnalité, veuillez ouvrir un ticket.
Puis-je utiliser ce package avec Gatsby ou CRA ?
Oui, à partir de la version 0.3.0.
Le script injecté est-il minifié ?
Oui.
Pourquoi resolvedTheme
est-il nécessaire ?
Lorsque vous prenez en charge la préférence de thème Système, vous voulez vous assurer que cela se reflète dans votre interface utilisateur. Cela signifie que vos boutons, sélections, listes déroulantes ou tout ce que vous utilisez pour indiquer le thème actuel doivent indiquer « Système » lorsque la préférence de thème Système est active.
Si nous ne faisions pas de distinction entre theme
et resolvedTheme
, l'interface utilisateur afficherait "Dark" ou "Light", alors qu'elle devrait en réalité être "System".
resolvedTheme
est alors utile pour modifier le comportement ou les styles au moment de l'exécution :
const { resolvedTheme } = useTheme ( )
< div style = { { color : resolvedTheme === 'dark' ? 'white' : 'black' } } >
Si nous n'avions pas resolvedTheme
et utilisé uniquement theme
, vous perdriez des informations sur l'état de votre interface utilisateur (vous sauriez seulement que le thème est "système", et non ce qu'il a résolu).