Fixing the Dark‑Mode Flash in Chakra UI on Next.js: A Diagnostic Guide
A diagnostic guide to Chakra UI's dark‑mode flash in Next.js: match the symptom to the cause, fix ColorModeScript placement, switch to cookie storage, and verify with JavaScript disabled.
10 Nov 2025, 22:21 UTC

The symptom: a light‑mode frame before dark mode kicks in
You hard‑refresh your Next.js app and, for a frame or two, the page renders in light mode before snapping to the dark mode the user actually chose. Sometimes it happens on every load; sometimes only on the first visit. Either way, the cause is the same family of problems: the server rendered one color mode, and the browser corrected it after hydration.
The useful takeaway: this flash is almost never a Chakra bug. It is a placement or storage‑configuration problem, and you can usually eliminate it entirely — but only if you understand which fix matches which symptom. This guide assumes Chakra UI v2 with Next.js; v3 restructured color mode around next‑themes, so check your installed major version before applying any snippet here (npm list @chakra-ui/react).
Why the flash happens
Chakra’s color mode preference lives in the browser — by default in localStorage under the key chakra-ui-color-mode. Your Next.js server cannot read localStorage, so during server‑side rendering (SSR) it renders with the theme’s initialColorMode (light, unless you changed it). The browser then reads the stored preference during hydration and flips the mode. That flip is the flash.
Chakra ships a mitigation: ColorModeScript, a small inline script that runs before first paint and applies the stored mode immediately. But it only works if it is placed correctly, and it cannot help the server render the right mode in the first place — for that you need cookie‑based storage.
Match the symptom to the cause
| What you observe | Likely cause | Fix direction |
|---|---|---|
| Flash on the very first visit only, then fine | ColorModeScript missing or placed after content | Add/move the script before app content |
| Flash on every hard refresh | localStorage storage manager with SSR; server always renders default mode | Switch to cookie‑based storage manager |
| Wrong mode stuck, no flash, toggle half‑works | initialColorMode mismatch, or multiple ChakraProvider instances | Align theme config; consolidate providers |
| Flash only in production build, not dev | Script order differs after bundling; Emotion cache duplication | Verify script placement in built HTML; check for duplicate Emotion caches |
Ordered checks
- Confirm the version. Run
npm list @chakra-ui/react nextin your project root. Everything below assumes Chakra v2. On v3, the color‑mode APIs differ — do not mix v2 snippets into a v3 app. - Reproduce reliably. Open devtools, throttle the network (e.g., "Slow 3G"), and hard‑refresh. Throttling stretches the gap between first paint and hydration so the flash is unmissable.
- Inspect the rendered HTML. View source (not the DOM inspector — you want the server HTML) and check whether the
<body>or<html>element already carries the dark‑mode class/attribute, and whether an inline color‑mode script appears before your app markup. - Check storage. In devtools, look at
localStorageforchakra-ui-color-modeand at cookies for the same key. localStorage‑only means the server is blind to the preference. - Count providers. Search the codebase for
ChakraProvider. More than one mounted instance (common in micro‑frontends, tests, or a provider accidentally left in a page component) means split color‑mode state.
Fix 1: Place ColorModeScript before your content
In the Pages Router, the script goes in pages/_document.tsx, inside <body> before <Main />:
// pages/_document.tsx (Pages Router, Chakra v2)
import { ColorModeScript } from '@chakra-ui/react';
import theme from '../theme';
// inside the Document render:
<body>
<ColorModeScript initialColorMode={theme.config.initialColorMode} />
<Main />
<NextScript />
</body>In the App Router, put it at the top of <body> in your root app/layout.tsx, before the provider‑wrapped children. Two details matter: the script must come before your app markup so it runs before first paint, and its initialColorMode prop must match your theme config exactly — a mismatch here is a classic source of "flash on first visit only."
Fix 2: Use cookie storage so the server renders the right mode
If the flash persists on every load even with the script placed correctly, the architectural fix is a cookie‑based storage manager. Because cookies are sent with the request, the server can read the preference and render the correct mode in the HTML itself — no client correction needed. Chakra v2 exposes cookieStorageManagerSSR for this; you pass it the cookie string from the incoming request (in getServerSideProps or middleware, depending on your setup) and hand it to ChakraProvider:
// Simplified Pages Router pattern, Chakra v2
import { ChakraProvider, cookieStorageManagerSSR, localStorageManager } from '@chakra-ui/react';
export function getServerSideProps({ req }) {
return { props: { cookies: req.headers.cookie ?? '' } };
}
export default function App({ Component, pageProps }) {
const manager = typeof pageProps.cookies === 'string'
? cookieStorageManagerSSR(pageProps.cookies)
: localStorageManager;
return (
<ChakraProvider theme={theme} colorModeManager={manager}>
<Component {...pageProps} />
</ChakraProvider>
);
}The exact wiring differs between Pages and App Router (App Router reads cookies via headers()/cookies() in a server component), so treat this as the pattern, not copy‑paste code, and verify against the v2 docs for your router.
Fix 3: Consolidate providers and align the theme config
If the mode is stuck wrong rather than flashing, check two things. First, your theme config:
const theme = extendTheme({
config: { initialColorMode: 'system', useSystemColorMode: true },
});Whatever you set here must be the same value you pass to ColorModeScript. Second, remove any stray nested ChakraProvider instances — each one keeps independent color‑mode state, so the toggle updates one tree while the rest of the page stays stale.
How to verify the fix
- Hard‑refresh under network throttling: no light‑mode frame should appear before dark mode applies.
- Disable JavaScript and reload: with cookie storage configured, the server‑rendered HTML should already carry the correct mode class. This is the strongest proof the SSR path works.
- Confirm the cookie (not just localStorage) now holds
chakra-ui-color-modeand that it matches the class on<html>/<body>.
When to escalate
If the flash survives correct script placement, cookie storage, and a single provider, suspect environment‑level issues: duplicated Emotion caches (two copies of @emotion/react in the bundle), a framework/Chakra version incompatibility, or a third‑party script injecting markup before ColorModeScript. At that point, reproduce the problem in a minimal sandbox (fresh Next.js app plus Chakra only) before filing an issue — a clean reproduction separates your integration from an actual library defect and gets you a useful answer much faster.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.