Abstraksi untuk tema di aplikasi React Anda.
appDir
Next.js 13useTheme
kait TemaLihat Contoh Langsung untuk mencobanya sendiri.
$ npm install next-themes
# or
$ yarn add next-themes
Anda memerlukan App
Khusus untuk menggunakan tema berikutnya. _app
paling sederhana terlihat seperti ini:
// pages/_app.js
function MyApp ( { Component , pageProps } ) {
return < Component { ... pageProps } / >
}
export default MyApp
Menambahkan dukungan mode gelap membutuhkan 2 baris kode:
// pages/_app.js
import { ThemeProvider } from 'next-themes'
function MyApp ( { Component , pageProps } ) {
return (
< ThemeProvider >
< Component { ... pageProps } / >
< / ThemeProvider >
)
}
export default MyApp
Anda harus memperbarui app/layout.jsx
untuk menggunakan tema berikutnya. layout
paling sederhana terlihat seperti ini:
// app/layout.jsx
export default function Layout ( { children } ) {
return (
< html >
< head / >
< body > { children } < / body >
< / html >
)
}
Menambahkan dukungan mode gelap membutuhkan 2 baris kode:
// app/layout.jsx
import { ThemeProvider } from 'next-themes'
export default function Layout ( { children } ) {
return (
< html suppressHydrationWarning >
< head / >
< body >
< ThemeProvider > { children } < / ThemeProvider >
< / body >
< / html >
)
}
Perhatikan bahwa ThemeProvider
adalah komponen klien, bukan komponen server.
Catatan! Jika Anda tidak menambahkan supresHydrationWarning ke
<html>
Anda akan mendapat peringatan karenanext-themes
memperbarui elemen tersebut. Properti ini hanya berlaku satu tingkat kedalaman, sehingga tidak akan memblokir peringatan hidrasi pada elemen lainnya.
Itu saja, aplikasi Next.js Anda sepenuhnya mendukung mode gelap, termasuk preferensi Sistem dengan prefers-color-scheme
. Tema juga langsung disinkronkan antar tab. Secara default, next-themes memodifikasi atribut data-theme
pada elemen html
, yang dapat Anda gunakan dengan mudah untuk menata gaya aplikasi Anda:
: root {
/* Your default theme */
--background : white;
--foreground : black;
}
[ data-theme = 'dark' ] {
--background : black;
--foreground : white;
}
Catatan! Jika Anda menyetel atribut Penyedia Tema ke kelas untuk Tailwind, tema berikutnya akan mengubah atribut
class
pada elemenhtml
. Lihat Dengan Tailwind.
UI Anda perlu mengetahui tema saat ini dan dapat mengubahnya. Kait useTheme
memberikan informasi 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 >
)
}
Peringatan! Kode di atas tidak aman untuk hidrasi dan akan memunculkan peringatan ketidakcocokan hidrasi saat dirender dengan SSG atau SSR. Hal ini dikarenakan kita tidak dapat mengetahui
theme
yang ada di server, sehingga akan selaluundefined
hingga dipasang di klien.Anda harus menunda rendering tema apa pun yang mengubah UI hingga dipasang di klien. Lihat contohnya.
Mari kita gali detailnya.
Semua konfigurasi tema Anda diteruskan ke ThemeProvider.
storageKey = 'theme'
: Kunci yang digunakan untuk menyimpan pengaturan tema di Penyimpanan lokaldefaultTheme = 'system'
: Nama tema default (untuk v0.0.12 dan lebih rendah, defaultnya adalah light
). Jika enableSystem
salah, tema defaultnya light
forcedTheme
: Nama tema yang dipaksakan untuk halaman saat ini (tidak mengubah pengaturan tema yang disimpan)enableSystem = true
: Apakah akan beralih antara dark
dan light
berdasarkan prefers-color-scheme
enableColorScheme = true
: Apakah akan menunjukkan kepada browser skema warna mana yang digunakan (gelap atau terang) untuk UI bawaan seperti input dan tomboldisableTransitionOnChange = false
: Nonaktifkan semua transisi CSS secara opsional saat berpindah tema (contoh)themes = ['light', 'dark']
: Daftar nama temaattribute = 'data-theme'
: Atribut HTML dimodifikasi berdasarkan tema aktifclass
dan data-*
(artinya atribut data apa pun, data-mode
, data-color
, dll.) (contoh)value
: Pemetaan opsional nama tema ke nilai atributobject
dengan key adalah nama tema dan value adalah nilai atribut (contoh)nonce
: Nonce opsional diteruskan ke tag script
yang dimasukkan, digunakan untuk mengizinkan skrip tema berikutnya di CSP AndascriptProps
: Alat peraga opsional untuk diteruskan ke tag script
yang dimasukkan (contoh)useTheme tidak mengambil parameter, tetapi mengembalikan:
theme
: Nama tema aktifsetTheme(name)
: Berfungsi untuk memperbarui tema. API ini identik dengan fungsi set yang dikembalikan oleh useState
-hook. Berikan nilai tema baru atau gunakan panggilan balik untuk menyetel tema baru berdasarkan tema saat ini.forcedTheme
: Tema halaman yang dipaksakan atau salah. Jika forcedTheme
disetel, Anda harus menonaktifkan UI peralihan tema apa punresolvedTheme
: Jika enableSystem
benar dan tema aktifnya adalah "system", ini akan mengembalikan apakah preferensi sistem ditetapkan ke "dark" atau "light". Kalau tidak, identik dengan theme
systemTheme
: Jika enableSystem
benar, mewakili preferensi tema Sistem ("gelap" atau "terang"), apa pun tema aktifnyathemes
: Daftar tema yang diteruskan ke ThemeProvider
(dengan "sistem" ditambahkan, jika enableSystem
benar)Tidak terlalu buruk, bukan? Mari kita lihat cara menggunakan properti ini dengan contoh:
Contoh Langsung menampilkan tema berikutnya yang sedang beraksi, dengan tema sistem gelap, terang, dan halaman dengan tema yang dipaksakan.
Untuk versi di atas v0.0.12, defaultTheme
secara otomatis disetel ke "sistem", jadi untuk menggunakan preferensi Sistem Anda cukup menggunakan:
< ThemeProvider >
Jika Anda tidak menginginkan tema Sistem, nonaktifkan melalui enableSystem
:
< ThemeProvider enableSystem = { false } >
Jika aplikasi Next.js Anda menggunakan kelas untuk menata halaman berdasarkan tema, ubah atribut prop menjadi class
:
< ThemeProvider attribute = "class" >
Sekarang, menyetel tema ke "gelap" akan menyetel class="dark"
pada elemen html
.
Katakanlah halaman pemasaran baru Anda yang keren hanya dalam mode gelap. Halaman tersebut harus selalu menggunakan tema gelap, dan mengubah tema tidak akan berpengaruh. Untuk memaksakan tema pada halaman Next.js Anda, cukup setel variabel pada komponen halaman:
// pages/awesome-page.js
const Page = ( ) => { ... }
Page . theme = 'dark'
export default Page
Di _app
Anda, baca variabel dan teruskan ke ThemeProvider:
function MyApp ( { Component , pageProps } ) {
return (
< ThemeProvider forcedTheme = { Component . theme || null } >
< Component { ... pageProps } / >
< / ThemeProvider >
)
}
Selesai! Halaman Anda selalu bertema gelap (terlepas dari preferensi pengguna), dan memanggil setTheme
dari useTheme
sekarang tidak perlu dilakukan. Namun, Anda harus memastikan untuk menonaktifkan UI Anda yang biasanya mengubah tema:
const { forcedTheme } = useTheme ( )
// Theme is forced, we shouldn't allow user to change the theme
const disabled = ! ! forcedTheme
Saya menulis tentang teknik ini di sini. Kita dapat menonaktifkan paksa semua transisi CSS sebelum tema diubah, dan segera mengaktifkannya kembali setelahnya. Hal ini memastikan UI Anda dengan durasi transisi yang berbeda tidak akan terasa tidak konsisten saat mengubah tema.
Untuk mengaktifkan perilaku ini, teruskan disableTransitionOnChange
:
< ThemeProvider disableTransitionOnChange >
Nama tema aktif digunakan sebagai nilai localStorage dan nilai atribut DOM. Jika nama temanya "pink", localStorage akan berisi theme=pink
dan DOMnya adalah data-theme="pink"
. Anda tidak dapat mengubah nilai localStorage, tetapi Anda dapat mengubah nilai DOM.
Jika kita ingin DOM merender data-theme="my-pink-theme"
ketika temanya "pink", teruskan value
prop:
< ThemeProvider value = { { pink : 'my-pink-theme' } } >
Selesai! Untuk lebih jelasnya, ini hanya mempengaruhi DOM. Berikut tampilan semua nilainya:
const { theme } = useTheme ( )
// => "pink"
localStorage . getItem ( 'theme' )
// => "pink"
document . documentElement . getAttribute ( 'data-theme' )
// => "my-pink-theme"
Rocket Loader adalah optimasi Cloudflare yang menunda pemuatan skrip inline dan eksternal untuk memprioritaskan konten situs web. Karena tema berikutnya bergantung pada injeksi skrip untuk menghindari layar berkedip saat memuat halaman, Rocket Loader merusak fungsi ini. Skrip individual dapat diabaikan dengan menambahkan atribut data-cfasync="false"
ke tag skrip:
< ThemeProvider scriptProps = { { 'data-cfasync' : 'false' } } >
tema berikutnya dirancang untuk mendukung sejumlah tema! Cukup berikan daftar tema:
< ThemeProvider themes = { [ 'pink' , 'red' , 'blue' ] } >
Catatan! Saat Anda meneruskan
themes
, kumpulan tema default ("terang" dan "gelap") akan diganti. Pastikan Anda menyertakannya jika Anda masih menginginkan tema terang dan gelap:
< ThemeProvider themes = { [ 'pink' , 'red' , 'blue' , 'light' , 'dark' ] } >
Untuk contoh cara menggunakan ini, lihat contoh multi-tema
Pustaka ini tidak bergantung pada gaya tema Anda menggunakan variabel CSS. Anda dapat melakukan hard-code nilai-nilai di CSS Anda, dan semuanya akan berfungsi seperti yang diharapkan (tanpa flashing apa pun):
html ,
body {
color : # 000 ;
background : # fff ;
}
[ data-theme = 'dark' ] ,
[ data-theme = 'dark' ] body {
color : # fff ;
background : # 000 ;
}
Tema Berikutnya sepenuhnya independen terhadap CSS, dapat digunakan dengan perpustakaan apa pun. Misalnya, dengan Komponen Bergaya Anda hanya perlu createGlobalStyle
di Aplikasi khusus Anda:
// 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 >
< / >
)
}
Karena kita tidak dapat mengetahui theme
di server, banyak nilai yang dikembalikan dari useTheme
tidak akan undefined
hingga dipasang di klien. Artinya, jika Anda mencoba merender UI berdasarkan tema saat ini sebelum dipasang di klien, Anda akan melihat kesalahan ketidakcocokan hidrasi.
Contoh kode berikut tidak aman :
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
Untuk memperbaikinya, pastikan Anda hanya merender UI yang menggunakan tema saat ini saat halaman dipasang di klien:
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
Alternatifnya, Anda dapat memuat komponen secara lambat di sisi klien. Contoh berikut menggunakan next/dynamic
tetapi Anda juga bisa menggunakan React.lazy
:
import dynamic from 'next/dynamic'
const ThemeSwitch = dynamic ( ( ) => import ( './ThemeSwitch' ) , { ssr : false } )
const ThemePage = ( ) => {
return (
< div >
< ThemeSwitch / >
< / div >
)
}
export default ThemePage
Untuk menghindari Pergeseran Tata Letak, pertimbangkan untuk merender kerangka/placeholder hingga dipasang di sisi klien.
Menampilkan gambar yang berbeda berdasarkan tema saat ini juga mengalami masalah ketidakcocokan hidrasi. Dengan next/image
Anda dapat menggunakan gambar kosong hingga tema terselesaikan:
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
Anda juga dapat menggunakan CSS untuk menyembunyikan atau menampilkan konten berdasarkan tema saat ini. Untuk menghindari ketidakcocokan hidrasi, Anda harus merender kedua versi UI, dengan CSS menyembunyikan versi yang tidak digunakan. Misalnya:
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;
}
Kunjungi contoh langsung • Lihat contoh kode sumber
CATATAN! Tailwind hanya mendukung mode gelap di versi >2.
Di tailwind.config.js
Anda, atur properti mode gelap ke selector
:
// tailwind.config.js
module . exports = {
darkMode : 'selector'
}
Catatan: Jika Anda menggunakan tailwindcss < 3.4.1 versi lama, gunakan 'class'
alih-alih 'selector'
Setel atribut Penyedia Tema Anda ke kelas:
// pages/_app.tsx
< ThemeProvider attribute = "class" >
Jika Anda menggunakan prop value untuk menentukan nilai atribut yang berbeda, pastikan tema gelap Anda secara eksplisit menggunakan nilai "gelap", seperti yang diwajibkan oleh Tailwind.
Itu saja! Sekarang Anda dapat menggunakan kelas khusus mode gelap:
< h1 className = "text-black dark:text-white" >
Tailwind juga memungkinkan Anda menggunakan pemilih khusus untuk mode gelap mulai v3.4.1.
Dalam hal ini, tailwind.config.js
Anda akan terlihat seperti ini:
// tailwind.config.js
module . exports = {
// data-mode is used as an example, next-themes supports using any data attribute
darkMode : [ 'selector' , '[data-mode="dark"]' ]
…
}
Sekarang atur atribut ThemeProvider Anda ke data-mode
:
// pages/_app.tsx
< ThemeProvider attribute = "data-mode" >
Dengan pengaturan ini, Anda kini dapat menggunakan kelas mode gelap Tailwind, seperti pada contoh sebelumnya:
ThemeProvider secara otomatis memasukkan skrip ke next/head
untuk memperbarui elemen html
dengan atribut yang benar sebelum halaman Anda lainnya dimuat. Ini berarti halaman tidak akan berkedip dalam keadaan apa pun, termasuk tema yang dipaksakan, tema sistem, banyak tema, dan penyamaran. Tidak diperlukan noflash.js
.
Mengapa halaman saya masih berkedip?
Dalam mode pengembang Next.js, halaman mungkin masih berkedip. Saat Anda membangun aplikasi dalam mode produksi, tidak akan ada flashing.
Mengapa saya mendapatkan kesalahan ketidakcocokan server/klien?
Saat menggunakan useTheme
, Anda akan melihat kesalahan ketidakcocokan hidrasi saat merender UI yang bergantung pada tema saat ini. Hal ini karena banyak nilai yang dikembalikan oleh useTheme
tidak ditentukan di server, karena kita tidak dapat membaca localStorage
hingga dipasang di klien. Lihat contoh cara memperbaiki kesalahan ini.
Apakah saya perlu menggunakan variabel CSS dengan perpustakaan ini?
Tidak. Lihat contohnya.
Bisakah saya mengatur atribut kelas atau data pada badan atau elemen lain?
Tidak. Jika Anda memiliki alasan bagus untuk mendukung fitur ini, silakan buka terbitan.
Bisakah saya menggunakan paket ini dengan Gatsby atau CRA?
Ya, mulai dari versi 0.3.0.
Apakah skrip yang dimasukkan diperkecil?
Ya.
Mengapa resolvedTheme
diperlukan?
Saat mendukung preferensi tema Sistem, Anda ingin memastikan hal itu tercermin di UI Anda. Ini berarti tombol, pilihan, dropdown, atau apa pun yang Anda gunakan untuk menunjukkan tema saat ini harus bertuliskan "Sistem" ketika preferensi tema Sistem aktif.
Jika kita tidak membedakan antara theme
dan resolvedTheme
, UI akan menampilkan "Gelap" atau "Terang", padahal seharusnya "Sistem".
resolvedTheme
kemudian berguna untuk mengubah perilaku atau gaya saat runtime:
const { resolvedTheme } = useTheme ( )
< div style = { { color : resolvedTheme === 'dark' ? 'white' : 'black' } } >
Jika kami tidak resolvedTheme
dan hanya menggunakan theme
, Anda akan kehilangan informasi tentang status UI Anda (Anda hanya akan mengetahui bahwa temanya adalah "sistem", dan bukan tujuannya).