Dark Mode in Next.js Without the Flash
September 24, 2026 Avishka Devinda
September 24, 2026 Avishka Devinda
Adding a dark mode button is easy.
Making dark mode feel correct on the first page load is more interesting.
One issue I have seen in Next.js projects is a quick light flash before the page switches to dark mode. It may only last a fraction of a second, but on a dark interface it is very noticeable.
The problem is usually not the toggle itself.
The problem is when the application discovers the user's theme.
Imagine the browser receives HTML that looks like the light theme.
Then React loads.
Then JavaScript reads localStorage.
Then the app realizes the saved theme is dark and adds the dark class.
For a moment, the browser already painted the light version.
That is the flash.
For Next.js projects, I often use next-themes.
A basic provider looks like:
'use client';
import { ThemeProvider } from 'next-themes';
export function Providers({
children,
}: {
children: React.ReactNode;
}) {
return (
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
>
{children}
</ThemeProvider>
);
}
Then Tailwind can use the dark class.
The theme library may change the html element before hydration.
That means the server-rendered HTML and the browser HTML can differ.
In the root layout I use:
<html lang="en" suppressHydrationWarning>
<body>{children}</body>
</html>
This does not fix bad hydration everywhere.
It tells React that this specific root-level theme difference is expected.
Another problem appears when a component immediately renders different markup based on the resolved theme.
For example:
const { resolvedTheme } = useTheme();
return resolvedTheme === 'dark'
? <MoonIcon />
: <SunIcon />;
During server rendering, the browser theme may not be known yet.
That can create mismatches.
For theme-specific controls, I either render a neutral initial state or wait until the component is mounted.
I do not want every component to ask JavaScript which theme is active.
Most styling should be CSS:
<div className="bg-white text-black dark:bg-black dark:text-white">
Content
</div>
JavaScript should control the theme state.
CSS should control how the theme looks.
This keeps components simpler and reduces theme-related rendering logic.
A dark theme is not automatically a black theme.
Many default palettes use blue-tinted grays.
If the product design is supposed to feel neutral, I explicitly choose neutral, zinc, stone, or custom grayscale tokens.
Theme quality is mostly about the design tokens, not the toggle.
Some assets look correct in light mode and disappear in dark mode.
I check:
Sometimes the right answer is a dark-mode asset variant.
Other times a CSS filter or currentColor-based SVG is enough.
The most important test is not clicking the toggle.
I test this:
That is where flashes and hydration issues usually become visible.
Dark mode is not only a color palette.
It is also part of the rendering lifecycle.
The browser needs to know the theme early enough that the first paint is already correct.
Using next-themes, class-based styling, careful hydration handling, and mostly CSS-driven theme styles gives me a setup that feels immediate instead of switching themes after the page appears.