'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 1:[1]

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
  }, [])

Solution 2:[2]

based on the documentation, ChakraUI doesn't sync color mode with LocalStorage automatically. you need to put <ColorModeScript /> first, so it sync with LocalStorage.

for basic React app, do this:

import { ColorModeScript } from '@chakra-ui/react'
// change to 'react-dom' if you are using React < 18
// here is React 18 example
import ReactDOM from 'react-dom/client'
import App from './App'
import theme from './theme'


// change to proper ReactDOM.render if you are using React < 18
// here is React 18 example
ReactDOM.createRoot(document.getElementById('root')!).render(
  <>
    {/* here is the magic */}
    <ColorModeScript initialColorMode={theme.config.initialColorMode} />

    <ChakraProvider theme={theme}>
      <App />
    </ChakraProvider>
  </>,
);

but don't forget to change useSystemColorMode to false, because this behavior will override any color with system color, and then fallback to LocalStorage (user preference theme) if system not available when it's configured to true. and then, set initialColorMode to 'system' so ChakraUI will fallback to system if user doesn't toggle color mode just yet.

export const theme = extendTheme({
  config: {
    initialColorMode: 'system',
    useSystemColorMode: false,
  },
});

but it somehow doesn't work for me when StrictMode is used.

and for NextJS, you can see the documentation for it.

Solution 3:[3]

FOR React ***

the easiest solution that I found is by default chakra store color-mode in local storage so you can access it by using localStorage.getItem('chakra-ui-color-mode') so create theme.js file

import { extendTheme } from "@chakra-ui/react";
const theme = extendTheme({
  config: {
    initialColorMode: localStorage.getItem('chakra-ui-color-mode')||'dark',
  },
});
export default theme;

this will try to fetch from local storage and if it didn't get value from local storage it will set the theme to dark then the local storage will be created when toggle is triggered so create index.js and app.js

index.js

import { ChakraProvider, CSSReset } from "@chakra-ui/react";
import React from "react";
import ReactDOM from "react-dom/client";
const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(
      <React.StrictMode>
        <ChakraProvider theme={theme}>
          <CSSReset />
            <App />
        </ChakraProvider>
      </React.StrictMode>
    ); 

app.js

import {
  Button,
  useColorMode,
} from "@chakra-ui/react";

const App = () => {
  const { colorMode, toggleColorMode } = useColorMode();
  return (
  <>
    <Button onClick={()=>toggleColorMode()}>Toggle</Button>
  </>
  );
};

export default App;

when you click toggle you will see in a localstorage value with a key('chakra-ui-color-mode')

** Note: This will also preserve across pages.**

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Nurudeen Amedu
Solution 2
Solution 3 Zablon