Uma abstração para temas em seu aplicativo React.
appDir
useTheme
o gancho do temaConfira o exemplo ao vivo para experimentar você mesmo.
$ npm install next-themes
# or
$ yarn add next-themes
Você precisará de um App
personalizado para usar os próximos temas. O _app
mais simples é assim:
// pages/_app.js
function MyApp ( { Component , pageProps } ) {
return < Component { ... pageProps } / >
}
export default MyApp
Adicionar suporte ao modo escuro requer 2 linhas de código:
// pages/_app.js
import { ThemeProvider } from 'next-themes'
function MyApp ( { Component , pageProps } ) {
return (
< ThemeProvider >
< Component { ... pageProps } / >
< / ThemeProvider >
)
}
export default MyApp
Você precisará atualizar seu app/layout.jsx
para usar os próximos temas. O layout
mais simples é assim:
// app/layout.jsx
export default function Layout ( { children } ) {
return (
< html >
< head / >
< body > { children } < / body >
< / html >
)
}
Adicionar suporte ao modo escuro requer 2 linhas 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 >
)
}
Observe que ThemeProvider
é um componente cliente, não um componente servidor.
Observação! Se você não adicionar suprimHydrationWarning ao seu
<html>
você receberá avisos porquenext-themes
atualiza esse elemento. Esta propriedade aplica-se apenas a um nível de profundidade, por isso não bloqueará os avisos de hidratação em outros elementos.
É isso, seu aplicativo Next.js oferece suporte total ao modo escuro, incluindo Preferência do sistema com prefers-color-scheme
. O tema também é sincronizado imediatamente entre as guias. Por padrão, next-themes modifica o atributo data-theme
no elemento html
, que você pode usar facilmente para estilizar seu aplicativo:
: root {
/* Your default theme */
--background : white;
--foreground : black;
}
[ data-theme = 'dark' ] {
--background : black;
--foreground : white;
}
Observação! Se você definir o atributo do seu provedor de tema como classe para Tailwind, os próximos temas modificarão o atributo
class
no elementohtml
. Consulte Com Tailwind.
Sua UI precisará conhecer o tema atual e ser capaz de alterá-lo. O gancho useTheme
fornece informações sobre o 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 >
)
}
Aviso! O código acima não é seguro para hidratação e emitirá um aviso de incompatibilidade de hidratação ao renderizar com SSG ou SSR. Isso ocorre porque não podemos conhecer o
theme
no servidor, portanto ele sempre seráundefined
até ser montado no cliente.Você deve atrasar a renderização de qualquer UI de alternância de tema até que seja montada no cliente. Veja o exemplo.
Vamos nos aprofundar nos detalhes.
Toda a configuração do seu tema é passada para ThemeProvider.
storageKey = 'theme'
: Chave usada para armazenar a configuração do tema no localStoragedefaultTheme = 'system'
: Nome do tema padrão (para v0.0.12 e inferior o padrão era light
). Se enableSystem
for falso, o tema padrão será light
forcedTheme
: Nome do tema forçado para a página atual (não modifica as configurações salvas do tema)enableSystem = true
: se deve alternar entre dark
e light
com base no prefers-color-scheme
enableColorScheme = true
: indica aos navegadores qual esquema de cores é usado (escuro ou claro) para UI integrada, como entradas e botõesdisableTransitionOnChange = false
: Opcionalmente, desative todas as transições CSS ao mudar de tema (exemplo)themes = ['light', 'dark']
: Lista de nomes de temasattribute = 'data-theme'
: atributo HTML modificado com base no tema ativoclass
e data-*
(ou seja, qualquer atributo de dados, data-mode
, data-color
, etc.) (exemplo)value
: mapeamento opcional do nome do tema para o valor do atributoobject
onde key é o nome do tema e value é o valor do atributo (exemplo)nonce
: nonce opcional passado para a tag script
injetada, usada para listar o script de próximos temas em seu CSPscriptProps
: adereços opcionais para passar para a tag script
injetada (exemplo)useTheme não aceita parâmetros, mas retorna:
theme
: nome do tema ativosetTheme(name)
: Função para atualizar o tema. A API é idêntica à função set retornada por useState
-hook. Passe o valor do novo tema ou use um retorno de chamada para definir o novo tema com base no tema atual.forcedTheme
: tema de página forçado ou falso. Se forcedTheme
estiver definido, você deve desativar qualquer UI de troca de temaresolvedTheme
: se enableSystem
for verdadeiro e o tema ativo for "sistema", retornará se a preferência do sistema foi resolvida como "escuro" ou "claro". Caso contrário, idêntico ao theme
systemTheme
: Se enableSystem
for verdadeiro, representa a preferência do tema do Sistema ("escuro" ou "claro"), independentemente de qual seja o tema ativothemes
: a lista de temas passada para ThemeProvider
(com "sistema" anexado, se enableSystem
for verdadeiro)Nada mal, certo? Vamos ver como usar essas propriedades com exemplos:
O exemplo ao vivo mostra os próximos temas em ação, com temas escuros, claros, de sistema e páginas com temas forçados.
Para versões acima de v0.0.12, o defaultTheme
é automaticamente definido como "sistema", portanto, para usar a preferência do sistema, você pode simplesmente usar:
< ThemeProvider >
Se você não quiser um tema do Sistema, desative-o via enableSystem
:
< ThemeProvider enableSystem = { false } >
Se seu aplicativo Next.js usa uma classe para estilizar a página com base no tema, altere o atributo prop para class
:
< ThemeProvider attribute = "class" >
Agora, definir o tema como "dark" definirá class="dark"
no elemento html
.
Digamos que sua nova página de marketing legal esteja apenas no modo escuro. A página deve sempre usar o tema escuro, e a alteração do tema não deve surtir efeito. Para forçar um tema em suas páginas Next.js, basta definir uma variável no componente da página:
// pages/awesome-page.js
const Page = ( ) => { ... }
Page . theme = 'dark'
export default Page
No seu _app
, leia a variável e passe para ThemeProvider:
function MyApp ( { Component , pageProps } ) {
return (
< ThemeProvider forcedTheme = { Component . theme || null } >
< Component { ... pageProps } / >
< / ThemeProvider >
)
}
Feito! Sua página é sempre um tema escuro (independentemente da preferência do usuário), e chamar setTheme
de useTheme
agora é autônomo. No entanto, você deve desativar qualquer interface de usuário que normalmente mudaria o tema:
const { forcedTheme } = useTheme ( )
// Theme is forced, we shouldn't allow user to change the theme
const disabled = ! ! forcedTheme
Escrevi sobre essa técnica aqui. Podemos desabilitar à força todas as transições CSS antes que o tema seja alterado e reativá-las imediatamente depois. Isso garante que sua IU com diferentes durações de transição não pareça inconsistente ao alterar o tema.
Para ativar esse comportamento, passe a propriedade disableTransitionOnChange
:
< ThemeProvider disableTransitionOnChange >
O nome do tema ativo é usado como valor localStorage e valor do atributo DOM. Se o nome do tema for "rosa", localStorage conterá theme=pink
e o DOM será data-theme="pink"
. Você não pode modificar o valor localStorage, mas pode modificar o valor DOM.
Se quisermos que o DOM renderize data-theme="my-pink-theme"
quando o tema for "rosa", passe a propriedade value
:
< ThemeProvider value = { { pink : 'my-pink-theme' } } >
Feito! Para ser mais claro, isso afeta apenas o DOM. Veja como ficarão todos os valores:
const { theme } = useTheme ( )
// => "pink"
localStorage . getItem ( 'theme' )
// => "pink"
document . documentElement . getAttribute ( 'data-theme' )
// => "my-pink-theme"
Rocket Loader é uma otimização Cloudflare que adia o carregamento de scripts inline e externos para priorizar o conteúdo do site. Como o next-themes depende de uma injeção de script para evitar o flash da tela no carregamento da página, o Rocket Loader quebra essa funcionalidade. Scripts individuais podem ser ignorados adicionando o atributo data-cfasync="false"
à tag de script:
< ThemeProvider scriptProps = { { 'data-cfasync' : 'false' } } >
next-themes foi projetado para suportar qualquer número de temas! Basta passar uma lista de temas:
< ThemeProvider themes = { [ 'pink' , 'red' , 'blue' ] } >
Observação! Quando você passa
themes
, o conjunto padrão de temas ("claro" e "escuro") é substituído. Certifique-se de incluí-los se ainda quiser seus temas claros e escuros:
< ThemeProvider themes = { [ 'pink' , 'red' , 'blue' , 'light' , 'dark' ] } >
Para um exemplo de como usar isso, confira o exemplo multitema
Esta biblioteca não depende do estilo do seu tema usando variáveis CSS. Você pode codificar os valores em seu CSS e tudo funcionará conforme o esperado (sem piscar):
html ,
body {
color : # 000 ;
background : # fff ;
}
[ data-theme = 'dark' ] ,
[ data-theme = 'dark' ] body {
color : # fff ;
background : # 000 ;
}
Next Themes é totalmente independente de CSS e funcionará com qualquer biblioteca. Por exemplo, com Styled Components você só precisa createGlobalStyle
em seu aplicativo personalizado:
// 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 >
< / >
)
}
Como não podemos conhecer o theme
no servidor, muitos dos valores retornados de useTheme
serão undefined
até serem montados no cliente. Isso significa que se você tentar renderizar a UI com base no tema atual antes de montar no cliente, verá um erro de incompatibilidade de hidratação.
O exemplo de código a seguir não é 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 corrigir isso, certifique-se de renderizar apenas a UI que usa o tema atual quando a página estiver montada no 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, você pode carregar lentamente o componente no lado do cliente. O exemplo a seguir usa next/dynamic
mas você também pode 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 a mudança de layout, considere renderizar um esqueleto/espaço reservado até ser montado no lado do cliente.
Mostrar imagens diferentes com base no tema atual também sofre com o problema de incompatibilidade de hidratação. Com next/image
você pode usar uma imagem vazia até que o tema seja resolvido:
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
Você também pode usar CSS para ocultar ou mostrar conteúdo com base no tema atual. Para evitar a incompatibilidade de hidratação, você precisará renderizar ambas as versões da UI, com CSS ocultando a versão não utilizada. Por exemplo:
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 o exemplo ao vivo • Veja o código-fonte do exemplo
OBSERVAÇÃO! Tailwind suporta apenas o modo escuro na versão >2.
Em seu tailwind.config.js
, defina a propriedade do modo escuro como selector
:
// tailwind.config.js
module . exports = {
darkMode : 'selector'
}
Nota: Se você estiver usando uma versão mais antiga do tailwindcss <3.4.1, use 'class'
em vez de 'selector'
Defina o atributo do seu Provedor de Tema para class:
// pages/_app.tsx
< ThemeProvider attribute = "class" >
Se você estiver usando a proposta value para especificar diferentes valores de atributos, certifique-se de que seu tema escuro use explicitamente o valor "dark", conforme exigido pelo Tailwind.
É isso! Agora você pode usar classes específicas do modo escuro:
< h1 className = "text-black dark:text-white" >
Tailwind também permite que você use um seletor personalizado para modo escuro a partir da v3.4.1.
Nesse caso, seu tailwind.config.js
ficaria assim:
// tailwind.config.js
module . exports = {
// data-mode is used as an example, next-themes supports using any data attribute
darkMode : [ 'selector' , '[data-mode="dark"]' ]
…
}
Agora defina o atributo do seu ThemeProvider para data-mode
:
// pages/_app.tsx
< ThemeProvider attribute = "data-mode" >
Com esta configuração, agora você pode usar as classes de modo escuro do Tailwind, como no exemplo anterior:
ThemeProvider injeta automaticamente um script em next/head
para atualizar o elemento html
com os atributos corretos antes que o restante da sua página seja carregado. Isso significa que a página não piscará em nenhuma circunstância, incluindo temas forçados, tema do sistema, temas múltiplos e navegação anônima. Não é necessário noflash.js
.
Por que minha página ainda está piscando?
No modo de desenvolvimento Next.js, a página ainda pode piscar. Quando você cria seu aplicativo no modo de produção, não haverá flashing.
Por que recebo um erro de incompatibilidade entre servidor/cliente?
Ao usar useTheme
, você verá um erro de incompatibilidade de hidratação ao renderizar a interface do usuário que depende do tema atual. Isso ocorre porque muitos dos valores retornados por useTheme
são indefinidos no servidor, já que não podemos ler localStorage
até a montagem no cliente. Veja o exemplo para saber como corrigir esse erro.
Preciso usar variáveis CSS com esta biblioteca?
Não. Veja o exemplo.
Posso definir a classe ou o atributo de dados no corpo ou em outro elemento?
Não. Se você tiver um bom motivo para oferecer suporte a esse recurso, abra um problema.
Posso usar este pacote com Gatsby ou CRA?
Sim, a partir da versão 0.3.0.
O script injetado está reduzido?
Sim.
Por que resolvedTheme
é necessário?
Ao oferecer suporte à preferência de tema do sistema, você deseja ter certeza de que isso se reflete na sua IU. Isso significa que seus botões, seleções, menus suspensos ou o que quer que você use para indicar o tema atual devem dizer “Sistema” quando a preferência de tema do Sistema estiver ativa.
Se não distinguíssemos entre theme
e resolvedTheme
, a UI mostraria "Dark" ou "Light", quando na verdade deveria ser "System".
resolvedTheme
é útil para modificar comportamentos ou estilos em tempo de execução:
const { resolvedTheme } = useTheme ( )
< div style = { { color : resolvedTheme === 'dark' ? 'white' : 'black' } } >
Se não tivéssemos resolvedTheme
e usássemos apenas theme
, você perderia informações sobre o estado da sua IU (você saberia apenas que o tema é "sistema" e não o que ele resolveu).