Fixing Dark Mode Flickering (FOUC) in React and Next.js
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
)
<!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.
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
pnpm install next-themesCreate a theme provider
"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
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
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.
Thanks for reading! ✨
Comments
Leave a comment or reaction below!