React 앱의 테마에 대한 추상화입니다.
appDir
지원useTheme
라이브 예제를 확인하여 직접 시도해 보세요.
$ npm install next-themes
# or
$ yarn add next-themes
다음 테마를 사용하려면 맞춤형 App
필요합니다. 가장 간단한 _app
다음과 같습니다.
// pages/_app.js
function MyApp ( { Component , pageProps } ) {
return < Component { ... pageProps } / >
}
export default MyApp
다크 모드 지원을 추가하려면 두 줄의 코드가 필요합니다.
// pages/_app.js
import { ThemeProvider } from 'next-themes'
function MyApp ( { Component , pageProps } ) {
return (
< ThemeProvider >
< Component { ... pageProps } / >
< / ThemeProvider >
)
}
export default MyApp
다음 테마를 사용하려면 app/layout.jsx
업데이트해야 합니다. 가장 간단한 layout
다음과 같습니다.
// app/layout.jsx
export default function Layout ( { children } ) {
return (
< html >
< head / >
< body > { children } < / body >
< / html >
)
}
다크 모드 지원을 추가하려면 두 줄의 코드가 필요합니다.
// app/layout.jsx
import { ThemeProvider } from 'next-themes'
export default function Layout ( { children } ) {
return (
< html suppressHydrationWarning >
< head / >
< body >
< ThemeProvider > { children } < / ThemeProvider >
< / body >
< / html >
)
}
ThemeProvider
는 서버 구성 요소가 아닌 클라이언트 구성 요소입니다.
메모!
<html>
에 억제HydrationWarning을 추가하지 않으면next-themes
해당 요소를 업데이트하기 때문에 경고가 표시됩니다. 이 속성은 한 수준 깊이에만 적용되므로 다른 요소에 대한 수화 경고를 차단하지 않습니다.
이것이 전부입니다. Next.js 앱은 prefers-color-scheme
사용한 시스템 기본 설정을 포함하여 다크 모드를 완벽하게 지원합니다. 테마는 탭 간에도 즉시 동기화됩니다. 기본적으로 next-themes는 앱 스타일을 지정하는 데 쉽게 사용할 수 있는 html
요소의 data-theme
속성을 수정합니다.
: root {
/* Your default theme */
--background : white;
--foreground : black;
}
[ data-theme = 'dark' ] {
--background : black;
--foreground : white;
}
메모! Theme Provider의 속성을 Tailwind의 클래스로 설정하면 다음 테마는
html
요소의class
속성을 수정합니다. Tailwind와 함께 참조하세요.
UI는 현재 테마를 알고 이를 변경할 수 있어야 합니다. useTheme
후크는 테마 정보를 제공합니다.
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 >
)
}
경고! 위 코드는 수화는 안전하지 않으며 SSG 또는 SSR로 렌더링할 때 수화 불일치 경고를 표시합니다. 이는 서버에서
theme
알 수 없기 때문에 클라이언트에 마운트될 때까지 항상undefined
되지 않기 때문입니다.클라이언트에 마운트될 때까지 테마 토글 UI 렌더링을 지연해야 합니다. 예를 참조하세요.
세부 사항을 파헤쳐 보겠습니다.
모든 테마 구성이 ThemeProvider로 전달됩니다.
storageKey = 'theme'
: localStorage에 테마 설정을 저장하는 데 사용되는 키defaultTheme = 'system'
: 기본 테마 이름(v0.0.12 이하의 경우 기본값은 light
). enableSystem
이 false인 경우 기본 테마는 light
입니다.forcedTheme
: 현재 페이지의 강제 테마 이름(저장된 테마 설정은 수정하지 않음)enableSystem = true
: prefers-color-scheme
에 따라 dark
과 light
사이를 전환할지 여부enableColorScheme = true
: 입력 및 버튼과 같은 내장 UI에 어떤 색 구성표가 사용되는지(어두운지 밝은지) 브라우저에 표시할지 여부disableTransitionOnChange = false
: 테마 전환 시 모든 CSS 전환을 선택적으로 비활성화합니다(예).themes = ['light', 'dark']
: 테마 이름 목록attribute = 'data-theme'
: 활성 테마를 기반으로 수정된 HTML 속성class
및 data-*
(모든 데이터 속성, data-mode
, data-color
등을 의미)를 허용합니다. (예)value
: 테마 이름을 속성 값으로 매핑하는 선택적 매핑object
입니다(예).nonce
: 삽입된 script
태그에 전달된 선택적 nonce는 CSP에서 다음 테마 스크립트를 허용 목록에 추가하는 데 사용됩니다.scriptProps
: 삽입된 script
태그에 전달할 선택적 소품(예)useTheme은 매개변수를 사용하지 않지만 다음을 반환합니다.
theme
: 활성 테마 이름setTheme(name)
: 테마를 업데이트하는 함수입니다. API는 useState
-hook에서 반환된 set 함수와 동일합니다. 새 테마 값을 전달하거나 콜백을 사용하여 현재 테마를 기반으로 새 테마를 설정합니다.forcedTheme
: 강제 페이지 테마 또는 거짓입니다. forcedTheme
설정된 경우 모든 테마 전환 UI를 비활성화해야 합니다.resolvedTheme
: enableSystem
이 true이고 활성 테마가 "system"인 경우 시스템 기본 설정이 "dark" 또는 "light"로 확인되었는지 여부를 반환합니다. 그렇지 않으면 theme
와 동일systemTheme
: enableSystem
이 true인 경우 활성 테마가 무엇인지에 관계없이 시스템 테마 기본 설정("어두움" 또는 "밝음")을 나타냅니다.themes
: ThemeProvider
에 전달된 테마 목록( enableSystem
이 true인 경우 "system"이 추가됨)별로 나쁘지 않죠? 예제를 통해 이러한 속성을 사용하는 방법을 살펴보겠습니다.
라이브 예제에서는 어두운 테마, 밝은 테마, 시스템 테마 및 강제 테마가 포함된 페이지 등 실제 동작 중인 다음 테마를 보여줍니다.
v0.0.12 이상의 버전에서는 defaultTheme
이 자동으로 "system"으로 설정되므로 시스템 기본 설정을 사용하려면 다음을 사용하면 됩니다.
< ThemeProvider >
시스템 테마를 원하지 않으면 enableSystem
통해 비활성화하십시오.
< ThemeProvider enableSystem = { false } >
Next.js 앱이 클래스를 사용하여 테마에 따라 페이지 스타일을 지정하는 경우 속성 prop을 class
로 변경합니다.
< ThemeProvider attribute = "class" >
이제 테마를 "dark"로 설정하면 html
요소에 class="dark"
설정됩니다.
귀하의 멋진 새 마케팅 페이지가 어두운 모드에만 있다고 가정해 보겠습니다. 페이지는 항상 어두운 테마를 사용해야 하며 테마를 변경해도 아무런 효과가 없어야 합니다. Next.js 페이지에 테마를 적용하려면 페이지 구성요소에 변수를 설정하기만 하면 됩니다.
// pages/awesome-page.js
const Page = ( ) => { ... }
Page . theme = 'dark'
export default Page
_app
에서 변수를 읽고 이를 ThemeProvider에 전달합니다.
function MyApp ( { Component , pageProps } ) {
return (
< ThemeProvider forcedTheme = { Component . theme || null } >
< Component { ... pageProps } / >
< / ThemeProvider >
)
}
완료! 귀하의 페이지는 항상 어두운 테마이며(사용자 기본 설정에 관계없이) useTheme
에서 setTheme
호출하는 것은 이제 작동하지 않습니다. 그러나 일반적으로 테마를 변경하는 UI를 비활성화해야 합니다.
const { forcedTheme } = useTheme ( )
// Theme is forced, we shouldn't allow user to change the theme
const disabled = ! ! forcedTheme
나는 이 기술에 대해 여기에 썼습니다. 테마가 변경되기 전에 모든 CSS 전환을 강제로 비활성화하고 그 후에 즉시 다시 활성화할 수 있습니다. 이렇게 하면 테마를 변경할 때 전환 기간이 다른 UI가 일관성이 없는 느낌을 받지 않게 됩니다.
이 동작을 활성화하려면 disableTransitionOnChange
소품을 전달하세요.
< ThemeProvider disableTransitionOnChange >
활성 테마의 이름은 localStorage 값과 DOM 속성 값 모두로 사용됩니다. 테마 이름이 "pink"인 경우 localStorage에는 theme=pink
포함되고 DOM은 data-theme="pink"
가 됩니다. localStorage 값은 수정할 수 없지만 DOM 값은 수정할 수 있습니다.
테마가 "pink"일 때 DOM이 대신 data-theme="my-pink-theme"
렌더링하도록 하려면 value
prop을 전달하세요.
< ThemeProvider value = { { pink : 'my-pink-theme' } } >
완료! 더 명확하게 말하면 이는 DOM에만 영향을 미칩니다. 모든 값은 다음과 같이 표시됩니다.
const { theme } = useTheme ( )
// => "pink"
localStorage . getItem ( 'theme' )
// => "pink"
document . documentElement . getAttribute ( 'data-theme' )
// => "my-pink-theme"
Rocket Loader는 웹 사이트 콘텐츠의 우선 순위를 지정하기 위해 인라인 및 외부 스크립트의 로드를 연기하는 Cloudflare 최적화입니다. 다음 테마는 페이지 로드 시 화면 깜박임을 방지하기 위해 스크립트 삽입에 의존하므로 Rocket Loader는 이 기능을 중단합니다. 스크립트 태그에 data-cfasync="false"
속성을 추가하면 개별 스크립트를 무시할 수 있습니다.
< ThemeProvider scriptProps = { { 'data-cfasync' : 'false' } } >
next-themes는 다양한 테마를 지원하도록 설계되었습니다! 간단히 테마 목록을 전달하세요.
< ThemeProvider themes = { [ 'pink' , 'red' , 'blue' ] } >
메모!
themes
전달하면 기본 테마 세트("light" 및 "dark")가 재정의됩니다. 여전히 밝은 테마와 어두운 테마를 원한다면 다음 항목을 포함하세요.
< ThemeProvider themes = { [ 'pink' , 'red' , 'blue' , 'light' , 'dark' ] } >
이를 사용하는 방법에 대한 예는 다중 테마 예를 확인하세요.
이 라이브러리는 CSS 변수를 사용하는 테마 스타일에 의존하지 않습니다. CSS에 값을 하드 코딩하면 모든 것이 예상대로 작동합니다(깜빡임 없이).
html ,
body {
color : # 000 ;
background : # fff ;
}
[ data-theme = 'dark' ] ,
[ data-theme = 'dark' ] body {
color : # fff ;
background : # 000 ;
}
다음 테마는 완전히 CSS와 독립적이며 모든 라이브러리에서 작동합니다. 예를 들어 스타일 구성 요소를 사용하면 사용자 정의 앱에서 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 >
< / >
)
}
서버의 theme
알 수 없기 때문에 useTheme
에서 반환된 많은 값은 클라이언트에 마운트될 때까지 undefined
. 즉, 클라이언트에 탑재하기 전에 현재 테마를 기반으로 UI를 렌더링하려고 하면 수화 불일치 오류가 표시됩니다.
다음 코드 샘플은 안전하지 않습니다 .
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
이 문제를 해결하려면 페이지가 클라이언트에 탑재될 때 현재 테마를 사용하는 UI만 렌더링해야 합니다.
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
또는 클라이언트 측에서 구성 요소를 지연 로드할 수도 있습니다. 다음 예제에서는 next/dynamic
사용하지만 React.lazy
사용할 수도 있습니다.
import dynamic from 'next/dynamic'
const ThemeSwitch = dynamic ( ( ) => import ( './ThemeSwitch' ) , { ssr : false } )
const ThemePage = ( ) => {
return (
< div >
< ThemeSwitch / >
< / div >
)
}
export default ThemePage
레이아웃 전환을 방지하려면 클라이언트 측에 마운트될 때까지 뼈대/자리 표시자를 렌더링하는 것이 좋습니다.
현재 테마에 따라 다른 이미지를 표시하는 것도 수분 불일치 문제로 어려움을 겪습니다. next/image
사용하면 테마가 해결될 때까지 빈 이미지를 사용할 수 있습니다.
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
CSS를 사용하여 현재 테마에 따라 콘텐츠를 숨기거나 표시할 수도 있습니다. 수화 불일치를 방지하려면 CSS가 사용되지 않는 버전을 숨겨 UI의 두 버전을 모두 렌더링해야 합니다. 예를 들어:
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;
}
실제 예제 방문하기 • 예제 소스 코드 보기
메모! Tailwind는 버전 2 이상에서만 어두운 모드를 지원합니다.
tailwind.config.js
에서 어두운 모드 속성을 selector
로 설정합니다.
// tailwind.config.js
module . exports = {
darkMode : 'selector'
}
참고: 이전 버전의 tailwindcss < 3.4.1을 사용하는 경우 'selector'
'class'
'를 사용하세요.
테마 공급자의 속성을 클래스로 설정합니다.
// pages/_app.tsx
< ThemeProvider attribute = "class" >
value prop을 사용하여 다양한 속성 값을 지정하는 경우 Tailwind에서 요구하는 대로 어두운 테마가 명시적으로 'dark' 값을 사용하는지 확인하세요.
그게 다야! 이제 어두운 모드 관련 클래스를 사용할 수 있습니다.
< h1 className = "text-black dark:text-white" >
Tailwind를 사용하면 v3.4.1부터 어두운 모드에 대한 맞춤 선택기를 사용할 수도 있습니다.
이 경우 tailwind.config.js
는 다음과 같습니다.
// tailwind.config.js
module . exports = {
// data-mode is used as an example, next-themes supports using any data attribute
darkMode : [ 'selector' , '[data-mode="dark"]' ]
…
}
이제 ThemeProvider의 속성을 data-mode
로 설정하세요.
// pages/_app.tsx
< ThemeProvider attribute = "data-mode" >
이 설정을 사용하면 이제 이전 예와 같이 Tailwind의 어두운 모드 클래스를 사용할 수 있습니다.
ThemeProvider는 페이지의 나머지 부분이 로드되기 전에 자동으로 스크립트를 next/head
에 삽입하여 올바른 속성으로 html
요소를 업데이트합니다. 즉, 강제 테마, 시스템 테마, 다중 테마, 시크릿 모드 등 어떤 상황에서도 페이지가 깜박이지 않습니다. noflash.js
필요하지 않습니다.
내 페이지가 계속 깜박이는 이유는 무엇입니까?
Next.js 개발 모드에서는 페이지가 여전히 깜박일 수 있습니다. 프로덕션 모드에서 앱을 빌드하면 깜박임이 발생하지 않습니다.
서버/클라이언트 불일치 오류가 발생하는 이유는 무엇입니까?
useTheme
사용하면 현재 테마에 의존하는 UI를 렌더링할 때 수화 불일치 오류가 표시됩니다. 이는 클라이언트에 마운트될 때까지 localStorage
읽을 수 없기 때문에 useTheme
에서 반환된 많은 값이 서버에서 정의되지 않았기 때문입니다. 이 오류를 해결하는 방법은 예를 참조하세요.
이 라이브러리에 CSS 변수를 사용해야 합니까?
아니요. 예를 참조하세요.
본문이나 다른 요소에 클래스나 데이터 속성을 설정할 수 있나요?
아니요. 이 기능을 지원해야 하는 타당한 이유가 있는 경우 이슈를 열어주세요.
이 패키지를 Gatsby 또는 CRA와 함께 사용할 수 있나요?
예, 0.3.0 버전부터 시작됩니다.
삽입된 스크립트가 축소되었나요?
예.
resolvedTheme
필요한 이유는 무엇입니까?
시스템 테마 기본 설정을 지원할 때 해당 내용이 UI에 반영되었는지 확인하고 싶을 것입니다. 이는 버튼, 선택, 드롭다운 또는 현재 테마를 나타내는 데 사용하는 모든 항목이 시스템 테마 기본 설정이 활성화된 경우 "시스템"으로 표시되어야 함을 의미합니다.
theme
과 resolvedTheme
를 구분하지 않으면 UI는 실제로 "System"이어야 하는데 "Dark" 또는 "Light"를 표시합니다.
resolvedTheme
은 런타임 시 동작이나 스타일을 수정하는 데 유용합니다.
const { resolvedTheme } = useTheme ( )
< div style = { { color : resolvedTheme === 'dark' ? 'white' : 'black' } } >
resolvedTheme
없고 theme
만 사용한 경우 UI 상태에 대한 정보를 잃게 됩니다(테마가 "시스템"이라는 것만 알 수 있고 해결된 내용은 알 수 없음).