問題: なにも設定しないと初期描画のときにカラーモードが反映されない
demo
このように、Chakra UI でなにも設定しないとリロード直後に一瞬ライトモードで表示されます。この記事ではこの問題を解消する方法を紹介します。
tl;dr
ColorModeScript を追加する + initialColorMode をセットする
'use client';
export const ChakraProviderClient = ({
children,
initialColorMode,
}: PropsWithChildren<{
initialColorMode: 'light' | 'dark';
}>) => (
ChakraProvider
colorModeManager={cookieStorageManager}
theme={extendTheme({ config: { initialColorMode } })}
ColorModeScript type="cookie"
{children}
ChakraProvider
);
対処1: ColorModeScript を追加する
demo
ColorModeScript をセットすると、最初から背景色や文字色が設定されるようになります。
(コンポーネントの色は一瞬ライトモードで表示されていますが、これは後述の initialColorMode で直ります)
ColorModeScript はこのように使います。
<ColorModeScript type="cookie" />
<ColorModeScript type="cookie" />
はページ読み込み時に cookie を見て body に light/dark クラスを追加してくれます(ColorModeScript の実装)。これによって背景色などがうまく機能するようになります。
対処2: initialColorMode をセットする
initialColorMode をセットすると、リロード時にもボタンの色が設定したカラーモードで表示されるようになります。(ColorModeScript との効果を比較するために、動画ではColorModeScript は抜いています)
demo
initialColorMode をセットしないと、サーバーレンダリング時点では設定で決まった色でレンダリングしてしまいます。そのため、設定したカラーモードで描画するために intialColorMode をセットするようにします。
<ChakraProvider
colorModeManager={cookieStorageManager}
theme={extendTheme({ config: { initialColorMode } })}
>
{children}
</ChakraProvider>
intialColorMode はサーバーサイドで cookies().get('chakra-ui-color-mode')?.value
で取得できます。あとはこれを ChakraProvider に渡せば完了…なのですが、ここで一つ注意すべき点があります。それは、Client component 内でないと theme={extendTheme({ config: { initialColorMode } })}
を直接渡せないということです。これは、extendTheme で返されるオブジェクトのプロパティに関数が混ざっていて「クライアントコンポーネントに直接関数を渡せない」という制限に引っかかるのが原因です。
これに引っかからないように、クライアントコンポーネントの中で ChakraProvider に theme を渡すようにしてください。
完成形
以上をまとめるとこのようになります。
'use client';
export const ChakraProviderClient = ({
children,
initialColorMode,
}: PropsWithChildren<{
initialColorMode: 'light' | 'dark';
}>) => (
ChakraProvider
colorModeManager={cookieStorageManager}
theme={extendTheme({ config: { initialColorMode } })}
ColorModeScript type="cookie"
{children}
ChakraProvider
);
import { cookies } from 'next/headers';
import { ChakraProviderClient } from './ChakraProviderClient';
export default function Layout({
children,
}: {
children: React.ReactNode;
}) {
return (
html lang='en'
body
ChakraProviderClient initialColorMode={cookies().get('chakra-ui-color-mode')?.value}
{children}
ChakraProviderClient
body
html
);
}
demo
一応動画も撮りましたが、表示が一切変わらないのでわかりづらい…