Chakra UI v2 useColorMode SSR hydration mismatch persists despite documented workarounds
0 reputation · 18 Mar 2021, 09:30 UTC
0 reputation · 18 Mar 2021, 09:30 UTC
Chakra UI v2's useColorMode hook manages dark/light theme state and persists the preference in localStorage. During server-side rendering with Next.js or Remix, the initial HTML renders without access to the client's persisted theme, causing a hydration mismatch when the client applies a different data-theme attribute than the server output.
The library documents this as a known trade-off and suggests a custom ColorModeScript or suppressing hydration warnings. However, the script approach requires inline execution, which conflicts with strict Content Security Policies. Chakra UI v2 is in maintenance mode with no fixes planned before v3, and the v3 RFC proposes a redesigned color mode architecture but lacks a release timeline or migration path.
Identify a reliable pattern to eliminate the hydration mismatch and flash of incorrect theme on first load without violating CSP or relying on unstable workarounds.
What combination of server-rendered theme detection and client-side synchronization avoids the mismatch entirely? Does any community-maintained fork or middleware approach provide a zero-configuration solution compatible with strict CSP? Is deferring useColorMode initialization until after hydration a viable strategy, and what are the accessibility implications?
35775 reputation · 18 Mar 2021, 16:21 UTC
To fully remove the hydration warning and the flash of the wrong theme, combine a server-side theme source (a cookie or the Accept-CH/Sec-CH-Prefers-Color-Scheme client hint) with a lightweight pre-hydration script that sets data-theme before React mounts. This works under a strict CSP because the script can be served as an external same-origin file, avoiding Chakra's inline ColorModeScript entirely.
localStorage value.localStorage, sees a different value, and writes a new data-theme attribute, triggering a warning and a visible flash.initialColorMode value via its theme config, which can be driven by server-rendered props.chakra-ui-color-mode) is a CSP-friendly way to persist the preference and make it readable on the server.document.documentElement.dataset.theme before React mounts without violating script-src 'self'.// Next.js example
export async function getServerSideProps({ req }) {
const cookie = req.headers.cookie?.match(/chakra-ui-color-mode=([^;]+)/);
const mode = cookie ? cookie[1] : null;
return { props: { initialColorMode: mode } };
}import { ChakraProvider, extendTheme } from '@chakra-ui/react';
export default function App({ Component, pageProps }) {
const theme = extendTheme({
config: { initialColorMode: pageProps.initialColorMode ?? 'system' }
});
return (
<ChakraProvider theme={theme}>
<Component {...pageProps} />
</ChakraProvider>
);
}import { useEffect } from 'react';
import { useColorMode } from '@chakra-ui/react';
function ThemeToggle() {
const { colorMode, toggleColorMode } = useColorMode();
useEffect(() => {
document.cookie = `chakra-ui-color-mode=${colorMode}; path=/; max-age=31536000`;
}, [colorMode]);
return <button onClick={toggleColorMode}>Toggle</button>;
}// public/color-mode.js
(function () {
var stored = localStorage.getItem('chakra-ui-color-mode');
if (stored === 'dark' || stored === 'light') {
document.documentElement.dataset.theme = stored;
} else {
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.dataset.theme = prefersDark ? 'dark' : 'light';
}
})();Include it in _document with <script src="/color-mode.js"></script> in the <head>. Serve it with Content-Type: application/javascript and ensure your CSP's script-src allows 'self'.
next-themes is a widely used zero-config option that handles cookie-based server detection plus client synchronization, and it can be adapted to drive Chakra's color mode. Verify its current CSP behavior against your policy before adopting.Deferring useColorMode initialization until after hydration removes the flash but leaves a short window with no theme applied, which can affect users relying on high-contrast or reduced-motion preferences. The cookie-plus-external-script pattern avoids that window entirely, which is why it is the recommended approach over deferred initialization.
Does your CSP permit a nonce or hash for inline scripts? If yes, you can keep the documented inline ColorModeScript with a nonce and skip the external file. If inline code is strictly forbidden, the external script above is the reliable path.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.