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
ダーク モードのサポートを追加するには、次の 2 行のコードが必要です。
// pages/_app.js
import { ThemeProvider } from 'next-themes'
function MyApp ( { Component , pageProps } ) {
return (
< ThemeProvider >
< Component { ... pageProps } / >
< / ThemeProvider >
)
}
export default MyApp
next-テーマを使用するには、 app/layout.jsx
更新する必要があります。最も単純なlayout
次のようになります。
// app/layout.jsx
export default function Layout ( { children } ) {
return (
< html >
< head / >
< body > { children } < / body >
< / html >
)
}
ダーク モードのサポートを追加するには、次の 2 行のコードが必要です。
// 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>
にsuppressHydrationWarningを追加しないと、next-themes
その要素を更新するため、警告が表示されます。このプロパティは 1 レベルの深さにのみ適用されるため、他の要素の水分補給警告はブロックされません。
以上で、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;
}
注記!テーマ プロバイダーの属性を Tailwind の class に設定すると、次のテーマは
html
要素のclass
属性を変更します。 「追い風あり」を参照してください。
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 の next-主題スクリプトを許可リストに登録するために使用されます。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 >
System テーマが不要な場合は、 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 値は変更できます。
テーマが「ピンク」のときに 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 は、Web サイトのコンテンツを優先するためにインラインおよび外部スクリプトの読み込みを遅らせる Cloudflare の最適化です。 next-theme はページ読み込み時の画面の点滅を避けるためにスクリプト インジェクションに依存しているため、Rocket Loader はこの機能を壊します。 data-cfasync="false"
属性を script タグに追加することで、個々のスクリプトを無視できます。
< ThemeProvider scriptProps = { { 'data-cfasync' : 'false' } } >
next-theme は、任意の数のテーマをサポートできるように設計されています。テーマのリストを渡すだけです。
< ThemeProvider themes = { [ 'pink' , 'red' , 'blue' ] } >
注記!
themes
渡すと、デフォルトのテーマのセット (「ライト」と「ダーク」) がオーバーライドされます。それでも明るいテーマと暗いテーマが必要な場合は、それらを必ず含めてください。
< 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 ;
}
Next Themes は 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 変数を使用する必要がありますか?
いいえ。例を参照してください。
body または別の要素に class または data 属性を設定できますか?
いいえ。この機能をサポートする正当な理由がある場合は、問題を作成してください。
このパッケージを Gatsby または CRA で使用できますか?
はい、バージョン 0.3.0 以降です。
挿入されたスクリプトは縮小されていますか?
はい。
なぜresolvedTheme
が必要なのでしょうか?
システム テーマ設定をサポートする場合、それが UI に反映されていることを確認する必要があります。これは、システム テーマ設定がアクティブな場合、ボタン、選択、ドロップダウン、または現在のテーマを示すために使用するものには「システム」と表示される必要があることを意味します。
theme
とresolvedTheme
を区別しないと、実際には「System」である必要があるときに、UI に「Dark」または「Light」と表示されてしまいます。
resolvedTheme
は、実行時の動作やスタイルを変更するのに役立ちます。
const { resolvedTheme } = useTheme ( )
< div style = { { color : resolvedTheme === 'dark' ? 'white' : 'black' } } >
もし、 resolvedTheme
を持たずにtheme
のみを使用した場合、UI の状態に関する情報が失われます (テーマが "system" であることだけがわかり、テーマの解決内容はわかりません)。