Eine Abstraktion für Themen in Ihrer React-App.
appDir
useTheme
HookSchauen Sie sich das Live-Beispiel an, um es selbst auszuprobieren.
$ npm install next-themes
# or
$ yarn add next-themes
Sie benötigen eine benutzerdefinierte App
, um next-themes verwenden zu können. Die einfachste _app
sieht so aus:
// pages/_app.js
function MyApp ( { Component , pageProps } ) {
return < Component { ... pageProps } / >
}
export default MyApp
Das Hinzufügen der Unterstützung für den Dunkelmodus erfordert zwei Codezeilen:
// pages/_app.js
import { ThemeProvider } from 'next-themes'
function MyApp ( { Component , pageProps } ) {
return (
< ThemeProvider >
< Component { ... pageProps } / >
< / ThemeProvider >
)
}
export default MyApp
Sie müssen Ihre app/layout.jsx
aktualisieren, um next-themes verwenden zu können. Das einfachste layout
sieht so aus:
// app/layout.jsx
export default function Layout ( { children } ) {
return (
< html >
< head / >
< body > { children } < / body >
< / html >
)
}
Das Hinzufügen der Unterstützung für den Dunkelmodus erfordert zwei Codezeilen:
// app/layout.jsx
import { ThemeProvider } from 'next-themes'
export default function Layout ( { children } ) {
return (
< html suppressHydrationWarning >
< head / >
< body >
< ThemeProvider > { children } < / ThemeProvider >
< / body >
< / html >
)
}
Beachten Sie, dass ThemeProvider
eine Clientkomponente und keine Serverkomponente ist.
Notiz! Wenn Sie suppressHydrationWarning nicht zu Ihrem
<html>
hinzufügen, erhalten Sie Warnungen, danext-themes
dieses Element aktualisiert. Diese Eigenschaft gilt nur eine Ebene tief, sodass Feuchtigkeitswarnungen für andere Elemente nicht blockiert werden.
Das ist alles, Ihre Next.js-App unterstützt den Dunkelmodus vollständig, einschließlich der Systemeinstellungen mit prefers-color-scheme
. Das Thema wird auch sofort zwischen den Registerkarten synchronisiert. Standardmäßig ändert next-themes das data-theme
Attribut des html
Elements, das Sie ganz einfach zum Gestalten Ihrer App verwenden können:
: root {
/* Your default theme */
--background : white;
--foreground : black;
}
[ data-theme = 'dark' ] {
--background : black;
--foreground : white;
}
Notiz! Wenn Sie das Attribut Ihres Theme-Anbieters für Tailwind auf „class“ setzen, ändert Next-Themes das
class
“-Attribut deshtml
Elements. Siehe Mit Rückenwind.
Ihre Benutzeroberfläche muss das aktuelle Thema kennen und in der Lage sein, es zu ändern. Der useTheme
Hook stellt Theme-Informationen bereit:
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 >
)
}
Warnung! Der obige Code ist nicht sicher in Bezug auf die Flüssigkeitszufuhr und löst beim Rendern mit SSG oder SSR eine Warnung aus, dass die Flüssigkeitszufuhr nicht übereinstimmt. Dies liegt daran, dass wir das
theme
auf dem Server nicht kennen können und es daher immerundefined
bleibt, bis es auf dem Client bereitgestellt wird.Sie sollten das Rendern der Benutzeroberfläche zum Umschalten eines Themes verzögern, bis es auf dem Client bereitgestellt wird. Siehe das Beispiel.
Schauen wir uns die Details an.
Ihre gesamte Designkonfiguration wird an ThemeProvider übergeben.
storageKey = 'theme'
: Schlüssel zum Speichern der Designeinstellungen in localStoragedefaultTheme = 'system'
: Name des Standardthemas (für v0.0.12 und niedriger war der Standardname light
). Wenn enableSystem
Wert „false“ hat, ist das Standardthema „ light
forcedTheme
: Erzwungener Theme-Name für die aktuelle Seite (ändert gespeicherte Theme-Einstellungen nicht)enableSystem = true
: Ob basierend auf prefers-color-scheme
zwischen dark
und light
gewechselt werden sollenableColorScheme = true
: Gibt an, ob Browsern angezeigt werden soll, welches Farbschema (dunkel oder hell) für integrierte Benutzeroberflächen wie Eingaben und Schaltflächen verwendet wirddisableTransitionOnChange = false
: Deaktiviert optional alle CSS-Übergänge beim Themenwechsel (Beispiel)themes = ['light', 'dark']
: Liste der Themennamenattribute = 'data-theme'
: HTML-Attribut basierend auf dem aktiven Theme geändertclass
und data-*
(d. h. jedes Datenattribut, data-mode
, data-color
usw.) (Beispiel)value
: Optionale Zuordnung des Themennamens zum Attributwertobject
wobei key der Name des Themas und value der Attributwert ist (Beispiel).nonce
: Optionale Nonce, die an das injizierte script
Tag übergeben wird und dazu verwendet wird, das Next-Themes-Skript in Ihrem CSP auf die Zulassungsliste zu setzenscriptProps
: Optionale Requisiten zur Übergabe an das injizierte script
Tag (Beispiel)useTheme akzeptiert keine Parameter, sondern gibt Folgendes zurück:
theme
: Name des aktiven ThemessetTheme(name)
: Funktion zum Aktualisieren des Themes. Die API ist identisch mit der von useState
-hook zurückgegebenen Set-Funktion. Übergeben Sie den neuen Designwert oder verwenden Sie einen Rückruf, um das neue Design basierend auf dem aktuellen Design festzulegen.forcedTheme
: Erzwungenes Seitenthema oder falsch. Wenn forcedTheme
festgelegt ist, sollten Sie alle Benutzeroberflächen zum Wechseln von Designs deaktivierenresolvedTheme
: Wenn enableSystem
“ wahr ist und das aktive Design „system“ ist, wird zurückgegeben, ob die Systemeinstellung auf „dunkel“ oder „hell“ aufgelöst wurde. Ansonsten identisch mit theme
systemTheme
: Wenn enableSystem
wahr ist, stellt dies die Systemdesignpräferenz („dunkel“ oder „hell“) dar, unabhängig vom aktiven Designthemes
: Die Liste der an ThemeProvider
übergebenen Themen (mit angehängtem „system“, wenn enableSystem
wahr ist)Nicht schlecht, oder? Sehen wir uns anhand von Beispielen an, wie diese Eigenschaften verwendet werden:
Das Live-Beispiel zeigt Next-Themes in Aktion, mit dunklen, hellen Systemthemen und Seiten mit erzwungenen Themen.
Für Versionen über v0.0.12 wird das defaultTheme
automatisch auf „system“ gesetzt. Um die Systemeinstellung zu verwenden, können Sie also einfach Folgendes verwenden:
< ThemeProvider >
Wenn Sie kein Systemthema wünschen, deaktivieren Sie es über enableSystem
:
< ThemeProvider enableSystem = { false } >
Wenn Ihre Next.js-App eine Klasse verwendet, um die Seite basierend auf dem Thema zu gestalten, ändern Sie das Attribut „prop“ in class
:
< ThemeProvider attribute = "class" >
Wenn Sie nun das Design auf „dunkel“ setzen, wird class="dark"
für das html
Element festgelegt.
Nehmen wir an, Ihre coole neue Marketingseite verfügt nur über den Dark Mode. Die Seite sollte immer das dunkle Thema verwenden und eine Änderung des Themas sollte keine Auswirkungen haben. Um ein Thema auf Ihren Next.js-Seiten zu erzwingen, legen Sie einfach eine Variable in der Seitenkomponente fest:
// pages/awesome-page.js
const Page = ( ) => { ... }
Page . theme = 'dark'
export default Page
Lesen Sie in Ihrer _app
die Variable und übergeben Sie sie an ThemeProvider:
function MyApp ( { Component , pageProps } ) {
return (
< ThemeProvider forcedTheme = { Component . theme || null } >
< Component { ... pageProps } / >
< / ThemeProvider >
)
}
Erledigt! Ihre Seite hat immer ein dunkles Thema (unabhängig von den Benutzerpräferenzen), und der Aufruf von setTheme
über useTheme
ist jetzt ein No-Op. Sie sollten jedoch darauf achten, alle Benutzeroberflächen zu deaktivieren, die normalerweise das Design ändern würden:
const { forcedTheme } = useTheme ( )
// Theme is forced, we shouldn't allow user to change the theme
const disabled = ! ! forcedTheme
Über diese Technik habe ich hier geschrieben. Wir können alle CSS-Übergänge zwangsweise deaktivieren, bevor das Design geändert wird, und sie unmittelbar danach wieder aktivieren. Dadurch wird sichergestellt, dass sich Ihre Benutzeroberfläche mit unterschiedlichen Übergangsdauern beim Ändern des Themas nicht inkonsistent anfühlt.
Um dieses Verhalten zu aktivieren, übergeben Sie die Requisite disableTransitionOnChange
:
< ThemeProvider disableTransitionOnChange >
Der Name des aktiven Themes wird sowohl als localStorage-Wert als auch als Wert des DOM-Attributs verwendet. Wenn der Theme-Name „pink“ lautet, enthält localStorage theme=pink
und das DOM lautet data-theme="pink"
. Sie können den localStorage-Wert nicht ändern, aber Sie können den DOM-Wert ändern.
Wenn wir möchten, dass das DOM stattdessen data-theme="my-pink-theme"
rendert, wenn das Thema „pink“ ist, übergeben Sie den value
prop:
< ThemeProvider value = { { pink : 'my-pink-theme' } } >
Erledigt! Um es noch deutlicher zu sagen: Dies betrifft nur das DOM. So sehen alle Werte aus:
const { theme } = useTheme ( )
// => "pink"
localStorage . getItem ( 'theme' )
// => "pink"
document . documentElement . getAttribute ( 'data-theme' )
// => "my-pink-theme"
Rocket Loader ist eine Cloudflare-Optimierung, die das Laden von Inline- und externen Skripten verzögert, um den Website-Inhalt zu priorisieren. Da next-themes auf eine Skriptinjektion angewiesen ist, um ein Blinken des Bildschirms beim Laden der Seite zu vermeiden, unterbricht Rocket Loader diese Funktionalität. Einzelne Skripte können ignoriert werden, indem das Attribut data-cfasync="false"
zum Skript-Tag hinzugefügt wird:
< ThemeProvider scriptProps = { { 'data-cfasync' : 'false' } } >
next-themes ist so konzipiert, dass es eine beliebige Anzahl von Themes unterstützt! Übergeben Sie einfach eine Themenliste:
< ThemeProvider themes = { [ 'pink' , 'red' , 'blue' ] } >
Notiz! Wenn Sie
themes
übergeben, werden die Standardthemen („hell“ und „dunkel“) überschrieben. Stellen Sie sicher, dass Sie diese einschließen, wenn Sie weiterhin helle und dunkle Themen haben möchten:
< ThemeProvider themes = { [ 'pink' , 'red' , 'blue' , 'light' , 'dark' ] } >
Ein Beispiel für die Verwendung finden Sie im Multi-Theme-Beispiel
Diese Bibliothek ist nicht darauf angewiesen, dass Ihr Theme CSS-Variablen verwendet. Sie können die Werte in Ihrem CSS fest codieren und alles wird wie erwartet funktionieren (ohne Blinken):
html ,
body {
color : # 000 ;
background : # fff ;
}
[ data-theme = 'dark' ] ,
[ data-theme = 'dark' ] body {
color : # fff ;
background : # 000 ;
}
Next Themes ist völlig CSS-unabhängig und funktioniert mit jeder Bibliothek. Bei Styled Components müssen Sie beispielsweise nur createGlobalStyle
:
// 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 >
< / >
)
}
Da wir das theme
auf dem Server nicht kennen können, bleiben viele der von useTheme
zurückgegebenen Werte undefined
, bis sie auf dem Client bereitgestellt werden. Das heißt, wenn Sie versuchen, die Benutzeroberfläche basierend auf dem aktuellen Design zu rendern, bevor Sie sie auf dem Client bereitstellen, wird ein Fehler bei der Nichtübereinstimmung der Flüssigkeitszufuhr angezeigt.
Das folgende Codebeispiel ist unsicher :
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
Um dies zu beheben, stellen Sie sicher, dass Sie nur die Benutzeroberfläche rendern, die das aktuelle Design verwendet, wenn die Seite auf dem Client bereitgestellt wird:
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
Alternativ können Sie die Komponente auch auf der Clientseite verzögert laden. Das folgende Beispiel verwendet next/dynamic
, Sie können aber auch React.lazy
verwenden:
import dynamic from 'next/dynamic'
const ThemeSwitch = dynamic ( ( ) => import ( './ThemeSwitch' ) , { ssr : false } )
const ThemePage = ( ) => {
return (
< div >
< ThemeSwitch / >
< / div >
)
}
export default ThemePage
Um eine Layoutverschiebung zu vermeiden, sollten Sie erwägen, ein Skelett/Platzhalter zu rendern, bis es auf der Clientseite bereitgestellt wird.
Das Anzeigen unterschiedlicher Bilder basierend auf dem aktuellen Thema leidet ebenfalls unter dem Problem der Fehlanpassung der Flüssigkeitszufuhr. Mit next/image
können Sie ein leeres Bild verwenden, bis das Thema gelöst ist:
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
Sie können CSS auch verwenden, um Inhalte basierend auf dem aktuellen Thema auszublenden oder anzuzeigen. Um die Nichtübereinstimmung der Hydratation zu vermeiden, müssen Sie beide Versionen der Benutzeroberfläche rendern, wobei CSS die nicht verwendete Version ausblendet. Zum Beispiel:
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;
}
Besuchen Sie das Live-Beispiel. • Sehen Sie sich den Beispielquellcode an
NOTIZ! Tailwind unterstützt den Dunkelmodus nur in Version >2.
Setzen Sie in Ihrer tailwind.config.js
die Dark Mode-Eigenschaft auf selector
:
// tailwind.config.js
module . exports = {
darkMode : 'selector'
}
Hinweis: Wenn Sie eine ältere Version von tailwindcss < 3.4.1 verwenden, verwenden Sie 'class'
anstelle von 'selector'
Legen Sie das Attribut für Ihren Theme-Anbieter auf „Klasse“ fest:
// pages/_app.tsx
< ThemeProvider attribute = "class" >
Wenn Sie die Wertstütze verwenden, um verschiedene Attributwerte anzugeben, stellen Sie sicher, dass Ihr dunkles Thema explizit den „dunklen“ Wert verwendet, wie von Tailwind gefordert.
Das ist es! Jetzt können Sie spezielle Klassen für den Dunkelmodus verwenden:
< h1 className = "text-black dark:text-white" >
Mit Tailwind können Sie ab Version 3.4.1 auch einen benutzerdefinierten Selektor für den Dunkelmodus verwenden.
In diesem Fall würde Ihre tailwind.config.js
so aussehen:
// tailwind.config.js
module . exports = {
// data-mode is used as an example, next-themes supports using any data attribute
darkMode : [ 'selector' , '[data-mode="dark"]' ]
…
}
Setzen Sie nun das Attribut für Ihren ThemeProvider auf data-mode
:
// pages/_app.tsx
< ThemeProvider attribute = "data-mode" >
Mit diesem Setup können Sie nun die Dark-Mode-Klassen von Tailwind verwenden, wie im vorherigen Beispiel:
ThemeProvider fügt automatisch ein Skript in next/head
ein, um das html
Element mit den richtigen Attributen zu aktualisieren, bevor der Rest Ihrer Seite geladen wird. Dies bedeutet, dass die Seite unter keinen Umständen blinkt, auch nicht bei erzwungenen Themen, Systemthemen, mehreren Themen und Inkognito. Kein noflash.js
erforderlich.
Warum blinkt meine Seite immer noch?
Im Next.js-Entwicklungsmodus blinkt die Seite möglicherweise immer noch. Wenn Sie Ihre App im Produktionsmodus erstellen, erfolgt kein Flashen.
Warum erhalte ich die Fehlermeldung „Server/Client-Nichtübereinstimmung“?
Wenn Sie useTheme
verwenden, wird beim Rendern einer Benutzeroberfläche, die auf dem aktuellen Design basiert, ein Fehler bei der Nichtübereinstimmung der Flüssigkeitszufuhr angezeigt. Dies liegt daran, dass viele der von useTheme
zurückgegebenen Werte auf dem Server undefiniert sind, da wir localStorage
erst lesen können, wenn wir es auf dem Client bereitstellen. Sehen Sie sich das Beispiel an, um zu erfahren, wie Sie diesen Fehler beheben können.
Muss ich mit dieser Bibliothek CSS-Variablen verwenden?
Nein. Siehe das Beispiel.
Kann ich das Klassen- oder Datenattribut für den Körper oder ein anderes Element festlegen?
Nein. Wenn Sie einen guten Grund für die Unterstützung dieser Funktion haben, eröffnen Sie bitte ein Problem.
Kann ich dieses Paket mit Gatsby oder CRA verwenden?
Ja, ab der Version 0.3.0.
Ist das eingefügte Skript minimiert?
Ja.
Warum ist resolvedTheme
notwendig?
Wenn Sie die Systemdesign-Präferenz unterstützen, möchten Sie sicherstellen, dass sich dies in Ihrer Benutzeroberfläche widerspiegelt. Das bedeutet, dass Ihre Schaltflächen, Auswahlmöglichkeiten, Dropdown-Menüs oder was auch immer Sie verwenden, um das aktuelle Design anzuzeigen, „System“ lauten sollten, wenn die System-Designeinstellung aktiv ist.
Wenn wir nicht zwischen theme
und resolvedTheme
unterscheiden würden, würde die Benutzeroberfläche „Dunkel“ oder „Hell“ anzeigen, obwohl es eigentlich „System“ sein sollte.
resolvedTheme
ist dann nützlich, um Verhalten oder Stile zur Laufzeit zu ändern:
const { resolvedTheme } = useTheme ( )
< div style = { { color : resolvedTheme === 'dark' ? 'white' : 'black' } } >
Wenn wir resolvedTheme
nicht hätten und nur theme
verwenden würden, würden Sie Informationen über den Status Ihrer Benutzeroberfläche verlieren (Sie wüssten nur, dass das Thema „System“ ist und nicht, in was es aufgelöst wurde).