Skip to Content
BlogFixing Dark Mode Flickering (FOUC) in React and Next.js

Fixing Dark Mode Flickering (FOUC) in React and Next.js

img

📸

Pexels

Flickering in React or Next.js dark mode—often referred to as a Flash of Unstyled Content (FOUC)—is a common issue where the page briefly displays light mode styles before switching to dark mode.

This flicker can be jarring and negatively impact the user experience, especially on slower networks or devices.

💡 What Causes Dark Mode Flickering?

1. Server-Side Rendering (SSR)

When using SSR (e.g., with Next.js), the server doesn’t know the user’s preferred color scheme since it’s not available in the document body which is only available in the window object that is not available in the server. As a result, the initial HTML renders with default (usually light) styles, and dark mode is only applied after hydration.

2. Slow JavaScript Loading

If the JavaScript responsible for setting the theme takes too long to execute, users will see the default light theme before it switches to dark mode. This is because the JavaScript is not executed until the DOM is fully loaded and the React app is hydrated.

3. FOUC (Flash of Unstyled Content)

This visual glitch occurs when the page briefly renders with default styles before the dark mode styles are applied, causing a flicker.


✅ How to Prevent Flickering

Let’s see how we can fix the flickering issue in React and Next.js. So we can provide a smooth transition between light, dark, and system themes. And better user experience.

1. Apply Styles in the <head>

Add an inline script or a class to the HTML that immediately applies the dark theme before the React app loads.

Example (Vite with index.html)

index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite + React + TS</title> </head> <script> try { // Get the theme from localStorage const theme = localStorage.getItem("theme"); // Get the system theme const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") .matches ? "dark" : "light"; // Get the effective theme const effectiveTheme = theme === "system" ? systemTheme : theme; // Add the theme to the document document.documentElement.classList.add(effectiveTheme); } catch (error) { console.error(error); } </script> <body> <div id="root"></div> <script type="module" src="/src/main.tsx"></script> </body> </html>

Example (Next.js with layout.tsx)

Or if you are using Next.js, you can use the the next <script> tag.

layout.tsx
export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode }>) { return ( <html lang='en' suppressHydrationWarning> <head> <script dangerouslySetInnerHTML={{ __html: ` try { const theme = localStorage.getItem("theme"); const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") .matches ? "dark" : "light"; const effectiveTheme = theme === "system" ? systemTheme : theme; document.documentElement.classList.add(effectiveTheme); } catch (error) { console.error(error); } `, }} /> </head> <body> {children} </body> </html> ) }

2. Use next-themes

You can use the next-themes library for your react or nextjs project and it works fine.

Install the next-themes package

pnpm install next-themes

Create a theme provider

components/theme-provider.tsx
"use client" import * as React from "react" import { ThemeProvider as NextThemesProvider } from "next-themes" export function ThemeProvider({ children, ...props }: React.ComponentProps<typeof NextThemesProvider>) { return <NextThemesProvider {...props}>{children}</NextThemesProvider> }

Wrap your root layout

layout.tsx
import { ThemeProvider } from "@/components/theme-provider" export default function RootLayout({ children }: RootLayoutProps) { return ( <> <html lang="en" suppressHydrationWarning> <head /> <body> <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange > {children} </ThemeProvider> </body> </html> </> ) }

Add a mode toggle

components/mode-toggle.tsx
import { useTheme } from "next-themes" export default function ModeToggle() { const {theme, setTheme} = useTheme(); return ( <div className="flex flex-col gap-4 items-center justify-center h-screen"> <h1 className="text-6xl font-bold">Hello World</h1> <h2 className="text-2xl font-bold border border-gray-300 rounded-md p-2"> Current Theme: {theme} </h2> <button className="bg-blue-500 text-white p-2 rounded-md" onClick={() => setTheme("system")} > Toggle Theme (system) </button> <button className="bg-blue-500 text-white p-2 rounded-md" onClick={() => setTheme("dark")} > Toggle Theme (dark) </button> <button className="bg-blue-500 text-white p-2 rounded-md" onClick={() => setTheme("light")} > Toggle Theme (light) </button> </div> ); }

And that’s it! 🎉

This will prevent the flickering issue and provide a smooth transition between light, dark, and system themes.

📌 Example

I have also prepared the Example of both React and Next.js. So checking it out will be helpful.

React Example

Next.js Example

Thanks for reading! ✨

Comments

Leave a comment or reaction below!

Last updated on