Search code examples
typescriptnext.jschakra-ui

Chakra UI color mode changes on page refresh, because of system color mode


I'm running NextJS with Chakra UI and the problem is when useSystemColorMode is set to true, it ignores the user's choice on page refresh and sets it back to the system color mode. For ex. system color mode is dark and the user toggles the button to light mode and then refreshes the page, which automatically undos the light mode and sets it back to dark. I expect it to remain light.

Note: When using system as initial color mode, the theme will change with the system preference. However, if another theme is manually selected by the user then that theme will be used on the next page load. To reset it to system preference, simply remove the chakra-ui-color-mode entry from localStorage.

What I expect is the initial color mode to be the system color mode (in my case = dark) and when the user toggles the button to light mode, on page refresh it should remain light.

pages\dogs.tsx (here is the toggle light/dark mode)

import { Button, useColorMode } from '@chakra-ui/react';
import Head from 'next/head';
import Link from 'next/link';

import { LoginWithCentredForm } from '@components/LoginWithCentredForm';

const Dogs = () => {
  const { colorMode, toggleColorMode } = useColorMode();

  return (
    <>
      <Head>
        <title>Dogs</title>
      </Head>
      <h1>Dogs</h1>
      <Button onClick={toggleColorMode}>
        Toggle {colorMode === 'light' ? 'Dark' : 'Light'}
      </Button>
      <h2>
        <Link href="/">
          <a>Go back home!</a>
        </Link>
        <LoginWithCentredForm />
      </h2>
    </>
  );
};

export default Dogs;

theme\index.tsx

import { extendTheme, ThemeConfig } from '@chakra-ui/react';

export const config: ThemeConfig = {
  initialColorMode: 'light',
  useSystemColorMode: true,
};

export const theme = extendTheme({
  config,
  fonts: {
    heading: 'Work Sans, system-ui, sans-serif',
    body: 'Inter, system-ui, sans-serif',
  },
});

pages\_documents.tsx

import { ColorModeScript } from '@chakra-ui/react';
import Document, { Html, Head, Main, NextScript } from 'next/document';

import { theme } from '../theme';

class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head>
          <link
            href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@700&family=Inter:wght@400;500;600;700&display=swap"
            rel="stylesheet"
          />
        </Head>
        <body>
          <ColorModeScript initialColorMode={theme.config.initialColorMode} />
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

pages\_app.tsx

import type { AppProps } from 'next/app';
import { ChakraProvider, CSSReset } from '@chakra-ui/react';

import { theme } from '../theme';

const App = ({ Component, pageProps }: AppProps) => {
  return (
    <ChakraProvider theme={theme}>
      <CSSReset />
      <Component {...pageProps} />
    </ChakraProvider>
  );
};

export default App;

Solution

  • I used useEffect to toggle the color mode to the correct one right after the component mounts with 1.5 seconds delay, I noticed it doesn't work without some delay.

    useEffect(() => {
        if (localStorage.getItem('chakra-ui-color-mode') === 'light' && colorMode === 'dark') {
          setTimeout(() => toggleColorMode(), 1500)
        } else if (localStorage.getItem('chakra-ui-color-mode') === 'dark' && colorMode === 'light') {
          setTimeout(() => toggleColorMode(), 1500)
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
      }, [])