<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged mui-v9</title>
<link>https://coderlegion.com/tag/mui-v9</link>
<description>Powered by Question2Answer</description>
<item>
<title>MUI v9 Theme Setup in Next.js 16: Full 2026 Guide</title>
<link>https://coderlegion.com/28671/mui-v9-theme-setup-in-next-js-16-full-2026-guide</link>
<description>&lt;p&gt;You installed &lt;code&gt;@mui/material&lt;/code&gt;, dropped a &lt;code&gt;&amp;lt;Button&amp;gt;&lt;/code&gt; on the page, and it looked... fine. Then you added a custom color, refreshed the page, and for a split second saw the default MUI blue before your color kicked in. Or worse — your styles randomly break depending on which route loads first.&lt;/p&gt;
&lt;p&gt;None of that is a coincidence. It's what happens when MUI's styling engine (Emotion) meets Next.js's server-streaming App Router without the right glue in between. MUI ships an official package specifically for this, and most tutorials online still show the old Pages Router setup or skip the dark mode flash entirely.&lt;/p&gt;
&lt;p&gt;By the end of this guide, you'll have a complete, production-ready MUI theme wired into a Next.js 16 App Router project — proper SSR style injection, a TypeScript-friendly custom theme, Google Fonts through &lt;code&gt;next/font&lt;/code&gt;, a flash-free light/dark mode toggle, and the correct way to write mode-aware styles so they don't flicker.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-is-tricky&quot;&gt;Why This Is Tricky&lt;/h3&gt;
&lt;p&gt;Next.js's App Router renders on the server and &lt;strong&gt;streams&lt;/strong&gt; HTML to the browser in chunks. MUI's default styling engine, Emotion, generates CSS at render time and needs to know how to collect all of that generated CSS and inject it into the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; the streamed HTML reaches the browser — otherwise you get unstyled flashes or duplicated styles.&lt;/p&gt;
&lt;p&gt;On top of that, MUI components that use hooks (like &lt;code&gt;useState&lt;/code&gt; for a theme toggle) need to be Client Components, but your root layout is a Server Component by default. If the &lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/server-vs-client-components-nextjs-16&quot;&gt;Server vs. Client Component split&lt;/a&gt; isn't second nature to you yet, it's worth a quick read before this guide — almost every mistake in a MUI + Next.js setup comes down to getting that boundary wrong. Get it wrong here and you'll see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A flash of unstyled or default-themed content (FOUC)&lt;/li&gt;
&lt;li&gt;Hydration mismatch warnings in the console&lt;/li&gt;
&lt;li&gt;Dark mode flashing light for a split second on every page load&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MUI's official &lt;code&gt;@mui/material-nextjs&lt;/code&gt; package exists specifically to solve the streaming/cache problem, and a few small App Router patterns solve the rest. Let's build it properly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;This guide assumes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Next.js 16&lt;/strong&gt; with the App Router&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;React 19.2&lt;/strong&gt; (Next.js 16's default)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript&lt;/strong&gt; in strict mode&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;npm&lt;/strong&gt; (swap for &lt;code&gt;yarn&lt;/code&gt;/&lt;code&gt;pnpm&lt;/code&gt; commands as needed)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-1-install-mui-and-its-dependencies&quot;&gt;Step 1: Install MUI and Its Dependencies&lt;/h3&gt;
&lt;p&gt;MUI's core package is &lt;code&gt;@mui/material&lt;/code&gt;, and it needs Emotion (its default style engine) plus the official Next.js integration package.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install @mui/material @emotion/react @emotion/styled @mui/material-nextjs @mui/icons-material
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's what each package actually does:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Package &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;@mui/material&lt;/code&gt; &lt;/td&gt;&lt;td&gt;The core component library (buttons, inputs, layout, etc.)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;@emotion/react&lt;/code&gt; / &lt;code&gt;@emotion/styled&lt;/code&gt; &lt;/td&gt;&lt;td&gt;MUI's default CSS-in-JS engine — generates the actual styles&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;@mui/material-nextjs&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Official adapter that makes Emotion's SSR streaming work correctly with the App Router&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;@mui/icons-material&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Material Design icon set (optional, but almost every project ends up needing it)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;&lt;p&gt;Version note: This guide targets MUI v9 with Next.js 16. Keep the MUI packages on compatible v9 releases, and use the &lt;code&gt;v16-appRouter&lt;/code&gt; integration entry point.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; MUI's current package layout uses Node.js package exports to provide appropriate ESM and CommonJS entry points, so this setup doesn't require special MUI bundler configuration.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;a-note-on-the-versioned-import-paths&quot;&gt;A note on the versioned import paths&lt;/h4&gt;
&lt;p&gt;You'll notice the import in the next step is &lt;code&gt;@mui/material-nextjs/v16-appRouter&lt;/code&gt;, not just &lt;code&gt;@mui/material-nextjs&lt;/code&gt;. This isn't cosmetic — the package ships a &lt;strong&gt;separate entry point per Next.js major version&lt;/strong&gt;, because each new Next.js major has occasionally changed how App Router streaming/rendering internals work under the hood. Using the wrong one for your Next.js version is a real (if often silently-working) mismatch, so pick the entry point that matches your installed Next.js major:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Next.js version &lt;/th&gt;&lt;th&gt;&lt;code&gt;@mui/material-nextjs&lt;/code&gt; entry point&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Next.js 13 &lt;/td&gt;&lt;td&gt;&lt;code&gt;v13-appRouter&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Next.js 14 &lt;/td&gt;&lt;td&gt;&lt;code&gt;v14-appRouter&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Next.js 15 &lt;/td&gt;&lt;td&gt;&lt;code&gt;v15-appRouter&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Next.js 16 &lt;/td&gt;&lt;td&gt;&lt;code&gt;v16-appRouter&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Copy-pasting an older tutorial's &lt;code&gt;v15-appRouter&lt;/code&gt; (or even older) import into a Next.js 16 project. It may still build without errors, but you're no longer using the entry point MUI actually maintains against your Next.js version — always match the number to your installed &lt;code&gt;next&lt;/code&gt; major.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-add-the-app-router-cache-provider&quot;&gt;Step 2: Add the App Router Cache Provider&lt;/h3&gt;
&lt;p&gt;This is the piece almost every outdated tutorial skips or gets wrong. &lt;code&gt;AppRouterCacheProvider&lt;/code&gt; is what collects Emotion's generated CSS during server rendering and streams it into the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; correctly, instead of leaving it to render inline in the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; (which causes the unstyled flash).&lt;/p&gt;
&lt;p&gt;Create your root layout:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/layout.tsx
import type { Metadata } from &quot;next&quot;;
import { AppRouterCacheProvider } from &quot;@mui/material-nextjs/v16-appRouter&quot;;

export const metadata: Metadata = {
  title: &quot;My MUI App&quot;,
  description: &quot;A Next.js 16 app styled with MUI&quot;,
};

export default function RootLayout({
  children,
}: Readonly&amp;lt;{ children: React.ReactNode }&amp;gt;) {
  return (
    &amp;lt;html lang=&quot;en&quot;&amp;gt;
      &amp;lt;body&amp;gt;
        &amp;lt;AppRouterCacheProvider&amp;gt;
          {children}
        &amp;lt;/AppRouterCacheProvider&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt; &lt;code&gt;AppRouterCacheProvider&lt;/code&gt; wraps everything under &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. As Next.js streams your page in chunks, this provider intercepts every bit of CSS Emotion generates along the way and inserts it into &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; ahead of the content that needs it — so by the time a chunk of HTML reaches the browser, its styles are already there.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;💡 If you also need to set page titles, Open Graph tags, or other &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; data alongside this setup, that's handled separately through Next.js's own &lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/nextjs-16-metadata&quot;&gt;Metadata API&lt;/a&gt; — the &lt;code&gt;metadata&lt;/code&gt; export above is a small example of it, not something &lt;code&gt;AppRouterCacheProvider&lt;/code&gt; manages.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At this point, run your dev server and add any MUI component — the styles should already apply correctly with no flash. Now let's make the theme actually yours.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;create-a-custom-theme-file&quot;&gt;Step 3: Create a Custom Theme File&lt;/h2&gt;
&lt;p&gt;A &quot;theme&quot; in MUI is just a JavaScript object describing your colors, typography, spacing, and component overrides. Centralizing it in one file means every component in your app pulls from the same design tokens instead of scattered inline styles.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// theme/theme.ts
&quot;use client&quot;;

import { createTheme } from &quot;@mui/material/styles&quot;;

const theme = createTheme({
  cssVariables: true,
  palette: {
    primary: {
      main: &quot;#0F62FE&quot;,
    },
    secondary: {
      main: &quot;#6929C4&quot;,
    },
  },
  shape: {
    borderRadius: 10,
  },
  typography: {
    fontFamily: &quot;var(--font-roboto)&quot;,
  },
});

export default theme;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&quot;use client&quot;&lt;/code&gt; is required at the top of this file. MUI recommends creating the theme in a Client Component module when using it with the App Router because the theme object is consumed by the client-side &lt;code&gt;ThemeProvider&lt;/code&gt;, and the theme contains more than plain serializable configuration data.&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/server-vs-client-components-nextjs-16&quot;&gt;Related: Server vs. Client Components in Next.js 16&lt;/a&gt; covers exactly what does and doesn't need this directive.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cssVariables: true&lt;/code&gt; tells MUI to expose theme tokens as CSS custom properties such as &lt;code&gt;--mui-palette-primary-main&lt;/code&gt;. MUI components can then reference these variables, which is particularly useful for switching color schemes without requiring a full CSS regeneration. This is the modern MUI approach — it plays nicer with server rendering and is what makes the flash-free dark mode in &lt;a rel=&quot;nofollow&quot; href=&quot;#add-flash-free-dark-mode&quot;&gt;Step 5&lt;/a&gt; possible.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;palette.primary&lt;/code&gt; / &lt;code&gt;palette.secondary&lt;/code&gt; override MUI's default blue/purple with your own brand colors. Every component (&lt;code&gt;&amp;lt;Button color=&quot;primary&quot;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;Chip color=&quot;secondary&quot;&amp;gt;&lt;/code&gt;, etc.) automatically picks these up.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;typography.fontFamily&lt;/code&gt; references a CSS variable we'll define in the next step using &lt;code&gt;next/font&lt;/code&gt; — this is the correct way to combine MUI with Next.js's font optimization.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; Keep this file focused on your app's design tokens (colors, spacing, typography), and use component-level &lt;code&gt;sx&lt;/code&gt; props for one-off styling. Don't try to theme every possible edge case up front — add overrides as you actually need them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-4-load-fonts-with-next-font-and-wire-up-the-theme-provider&quot;&gt;Step 4: Load Fonts with &lt;code&gt;next/font&lt;/code&gt; and Wire Up the Theme Provider&lt;/h3&gt;
&lt;p&gt;Next.js's built-in font optimization (&lt;code&gt;next/font&lt;/code&gt;) self-hosts Google Fonts at build time, avoiding a render-blocking request to Google's font CDN. MUI's docs specifically recommend combining it with the theme via a CSS variable rather than passing the font name as a plain string.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/layout.tsx
import type { Metadata } from &quot;next&quot;;
import { Roboto } from &quot;next/font/google&quot;;
import { AppRouterCacheProvider } from &quot;@mui/material-nextjs/v16-appRouter&quot;;
import { ThemeProvider } from &quot;@mui/material/styles&quot;;
import CssBaseline from &quot;@mui/material/CssBaseline&quot;;
import theme from &quot;@/theme/theme&quot;;

const roboto = Roboto({
  weight: [&quot;300&quot;, &quot;400&quot;, &quot;500&quot;, &quot;700&quot;],
  subsets: [&quot;latin&quot;],
  display: &quot;swap&quot;,
  variable: &quot;--font-roboto&quot;,
});

export const metadata: Metadata = {
  title: &quot;My MUI App&quot;,
  description: &quot;A Next.js 16 app styled with MUI&quot;,
};

export default function RootLayout({
  children,
}: Readonly&amp;lt;{ children: React.ReactNode }&amp;gt;) {
  return (
    &amp;lt;html lang=&quot;en&quot; className={roboto.variable}&amp;gt;
      &amp;lt;body&amp;gt;
        &amp;lt;AppRouterCacheProvider&amp;gt;
          &amp;lt;ThemeProvider theme={theme}&amp;gt;
            &amp;lt;CssBaseline /&amp;gt;
            {children}
          &amp;lt;/ThemeProvider&amp;gt;
        &amp;lt;/AppRouterCacheProvider&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Roboto({ ... variable: &quot;--font-roboto&quot; })&lt;/code&gt; generates a CSS variable holding the correct font-family string, and Next.js injects the actual &lt;code&gt;@font-face&lt;/code&gt; rules automatically — no manual &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tags.&lt;/li&gt;
&lt;li&gt;Adding &lt;code&gt;className={roboto.variable}&lt;/code&gt; to &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; makes that CSS variable available everywhere in your app, including inside the MUI theme file from &lt;a rel=&quot;nofollow&quot; href=&quot;#create-a-custom-theme-file&quot;&gt;Step 3&lt;/a&gt; (&lt;code&gt;fontFamily: &quot;var(--font-roboto)&quot;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;ThemeProvider theme={theme}&amp;gt;&lt;/code&gt; makes your custom theme available to every MUI component via context — this is what actually applies your colors and typography.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;CssBaseline /&amp;gt;&lt;/code&gt; is MUI's version of a CSS reset — it normalizes margins, sets a consistent &lt;code&gt;box-sizing&lt;/code&gt;, and applies your theme's background/text colors to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. Almost every MUI project should include it once, at the root.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Passing &lt;code&gt;theme={theme}&lt;/code&gt; where &lt;code&gt;theme&lt;/code&gt; was created &lt;em&gt;without&lt;/em&gt; &lt;code&gt;&quot;use client&quot;&lt;/code&gt; in its own file, then importing it into a Server Component layout directly. Keep the theme creation itself client-side (as shown in &lt;a rel=&quot;nofollow&quot; href=&quot;#create-a-custom-theme-file&quot;&gt;Step 3&lt;/a&gt;) to avoid subtle serialization issues — &lt;code&gt;ThemeProvider&lt;/code&gt; here is already a Client Component internally, so this composition is safe.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At this point, your fonts, colors, and typography are fully wired up with zero flash of unstyled content. Now let's add dark mode.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;add-flash-free-dark-mode&quot;&gt;Step 5: Add Flash-Free Dark Mode&lt;/h2&gt;
&lt;p&gt;The trickiest part of theming in Next.js isn't picking colors — it's avoiding the flash where a user's saved &quot;dark mode&quot; preference briefly shows light mode before JavaScript hydrates and corrects it. MUI's &lt;code&gt;cssVariables&lt;/code&gt; mode ships a dedicated component for exactly this: &lt;code&gt;InitColorSchemeScript&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First, update the theme to support both color schemes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// theme/theme.ts
&quot;use client&quot;;

import { createTheme } from &quot;@mui/material/styles&quot;;

const theme = createTheme({
  cssVariables: {
    colorSchemeSelector: &quot;class&quot;,
  },
  colorSchemes: {
    light: {
      palette: {
        primary: { main: &quot;#0F62FE&quot; },
        secondary: { main: &quot;#6929C4&quot; },
      },
    },
    dark: {
      palette: {
        primary: { main: &quot;#7CA9FF&quot; },
        secondary: { main: &quot;#B490E8&quot; },
      },
    },
  },
  shape: {
    borderRadius: 10,
  },
  typography: {
    fontFamily: &quot;var(--font-roboto)&quot;,
  },
});

export default theme;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What changed:&lt;/strong&gt; instead of one flat &lt;code&gt;palette&lt;/code&gt;, we now define &lt;code&gt;colorSchemes.light&lt;/code&gt; and &lt;code&gt;colorSchemes.dark&lt;/code&gt;, each with its own palette. &lt;code&gt;colorSchemeSelector: &quot;class&quot;&lt;/code&gt; tells MUI to switch schemes by toggling a CSS class (rather than &lt;code&gt;data-&lt;/code&gt; attributes or &lt;code&gt;prefers-color-scheme&lt;/code&gt; alone), which gives you full manual control over the toggle.&lt;/p&gt;
&lt;p&gt;Next, add &lt;code&gt;InitColorSchemeScript&lt;/code&gt; to the root layout, &lt;strong&gt;before&lt;/strong&gt; anything else renders:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/layout.tsx
import { InitColorSchemeScript } from &quot;@mui/material&quot;;
// ...other imports from Step 4

export default function RootLayout({
  children,
}: Readonly&amp;lt;{ children: React.ReactNode }&amp;gt;) {
  return (
    &amp;lt;html lang=&quot;en&quot; className={roboto.variable} suppressHydrationWarning&amp;gt;
      &amp;lt;body&amp;gt;
        &amp;lt;InitColorSchemeScript attribute=&quot;class&quot; /&amp;gt;
        &amp;lt;AppRouterCacheProvider&amp;gt;
          &amp;lt;ThemeProvider theme={theme}&amp;gt;
            &amp;lt;CssBaseline /&amp;gt;
            {children}
          &amp;lt;/ThemeProvider&amp;gt;
        &amp;lt;/AppRouterCacheProvider&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt; &lt;code&gt;InitColorSchemeScript&lt;/code&gt; injects a tiny, synchronous inline script that runs &lt;strong&gt;before&lt;/strong&gt; React hydrates. It reads the user's saved preference (from &lt;code&gt;localStorage&lt;/code&gt;) and applies the right class to &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; immediately — so the correct theme is already active by the time the browser paints anything. This is exactly what eliminates the flash. If you want the deeper &quot;why&quot; behind pre-hydration scripts like this one, &lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/nextjs-rendering-strategies-explained&quot;&gt;Next.js Rendering Strategies Explained&lt;/a&gt; covers how streaming and hydration fit together.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ If you don't add &lt;code&gt;suppressHydrationWarning&lt;/code&gt; to your &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; tag, you will see warnings about &quot;Extra attributes from the server&quot; because &lt;code&gt;InitColorSchemeScript&lt;/code&gt; updates that element.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now build a simple toggle button as a Client Component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// components/theme-toggle.tsx
&quot;use client&quot;;

import IconButton from &quot;@mui/material/IconButton&quot;;
import Brightness4Icon from &quot;@mui/icons-material/Brightness4&quot;;
import Brightness7Icon from &quot;@mui/icons-material/Brightness7&quot;;
import { useColorScheme } from &quot;@mui/material/styles&quot;;

export default function ThemeToggle() {
  const { mode, setMode } = useColorScheme();

  if (!mode) {
    // Renders on the server before the client has resolved a mode —
    // returning null here avoids a hydration mismatch.
    return null;
  }

  return (
    &amp;lt;IconButton
      onClick={() =&amp;gt; setMode(mode === &quot;light&quot; ? &quot;dark&quot; : &quot;light&quot;)}
      color=&quot;inherit&quot;
      aria-label=&quot;Toggle light and dark theme&quot;
    &amp;gt;
      {mode === &quot;dark&quot; ? &amp;lt;Brightness7Icon /&amp;gt; : &amp;lt;Brightness4Icon /&amp;gt;}
    &amp;lt;/IconButton&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt; &lt;code&gt;useColorScheme()&lt;/code&gt; is a hook MUI provides specifically for &lt;code&gt;cssVariables&lt;/code&gt;-based themes — it gives you the current &lt;code&gt;mode&lt;/code&gt; and a &lt;code&gt;setMode&lt;/code&gt; function that updates both the DOM class and &lt;code&gt;localStorage&lt;/code&gt; for you. The &lt;code&gt;if (!mode) return null&lt;/code&gt; guard matters: on the very first client render, &lt;code&gt;mode&lt;/code&gt; can briefly be &lt;code&gt;undefined&lt;/code&gt; before MUI resolves it, and rendering nothing during that split second avoids a server/client mismatch warning.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; &lt;code&gt;mode&lt;/code&gt; can be &lt;code&gt;&quot;light&quot;&lt;/code&gt;, &lt;code&gt;&quot;dark&quot;&lt;/code&gt;, or &lt;code&gt;&quot;system&quot;&lt;/code&gt;. If you want a three-way toggle (light/dark/follow OS), cycle through all three values instead of just two in the &lt;code&gt;onClick&lt;/code&gt; handler.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;write-mode-aware-styles-the-right-way&quot;&gt;Step 6: Write Mode-Aware Styles the Right Way — Avoid `theme.palette.mode`&lt;/h2&gt;
&lt;p&gt;Here's a mistake that's easy to make even after everything above is set up correctly: once a component needs a color that depends on light vs. dark mode, it's tempting to reach for a plain conditional:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// ❌ Don't do this with CSS variables enabled
&amp;lt;Card
  sx={(theme) =&amp;gt; ({
    backgroundColor: theme.palette.mode === &quot;dark&quot; ? &quot;#000&quot; : &quot;#fff&quot;,
  })}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This looks harmless, but MUI's own docs explicitly warn against it: &lt;code&gt;theme.palette.mode&lt;/code&gt; reflects whatever mode was resolved &lt;strong&gt;at render time&lt;/strong&gt;, and on the server that's always a guess (or the default). The result is the exact SSR flicker you just spent &lt;a rel=&quot;nofollow&quot; href=&quot;#add-flash-free-dark-mode&quot;&gt;Step 5&lt;/a&gt; eliminating — the server renders one color, the client corrects it a moment later, and you see a flash.&lt;/p&gt;
&lt;p&gt;The fix is &lt;code&gt;theme.applyStyles()&lt;/code&gt;, which generates real CSS rules scoped to each mode instead of picking one value at render time:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// ✅ Correct — no flicker, works with CSS variables
import Card from &quot;@mui/material/Card&quot;;

&amp;lt;Card
  sx={[
    {
      backgroundColor: &quot;#fff&quot;,
    },
    (theme) =&amp;gt;
      theme.applyStyles(&quot;dark&quot;, {
        backgroundColor: &quot;#000&quot;,
      }),
  ]}
/&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt; instead of computing a single color based on the &lt;em&gt;current&lt;/em&gt; mode, &lt;code&gt;applyStyles(&quot;dark&quot;, {...})&lt;/code&gt; generates mode-specific CSS that is activated by the color-scheme selector configured in the theme. Both the light and dark rules can exist in the stylesheet, while the active selector on &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; determines which styles apply.&lt;/p&gt;
&lt;p&gt;The same pattern works with the &lt;code&gt;styled()&lt;/code&gt; API:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// components/themed-box.tsx
&quot;use client&quot;;

import { styled } from &quot;@mui/material/styles&quot;;

export const ThemedBox = styled(&quot;div&quot;)(({ theme }) =&amp;gt; [
  {
    color: &quot;#111&quot;,
    backgroundColor: theme.palette.background.paper,
  },
  theme.applyStyles(&quot;dark&quot;, {
    color: &quot;#fff&quot;,
    backgroundColor: theme.palette.grey[900],
  }),
]);
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Sprinkling &lt;code&gt;theme.palette.mode === &quot;dark&quot;&lt;/code&gt; checks throughout &lt;code&gt;sx&lt;/code&gt; props &quot;just to get something working,&quot; then wondering why dark mode flickers even after adding &lt;code&gt;InitColorSchemeScript&lt;/code&gt;. &lt;code&gt;InitColorSchemeScript&lt;/code&gt; only prevents the &lt;em&gt;class&lt;/em&gt; on &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; from being wrong on first paint — it does nothing to fix a component that's still choosing its color at render time instead of via CSS. Audit your &lt;code&gt;sx&lt;/code&gt; props for &lt;code&gt;palette.mode&lt;/code&gt; checks and swap them for &lt;code&gt;applyStyles()&lt;/code&gt; as you find them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; You can pass an array of style objects (as shown above) or plain objects — &lt;code&gt;theme.applyStyles()&lt;/code&gt; composes fine with any other static styles in the same &lt;code&gt;sx&lt;/code&gt; array.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-7-verify-everything-works&quot;&gt;Step 7: Verify Everything Works&lt;/h3&gt;
&lt;p&gt;Drop the toggle into a page and confirm the full setup:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/page.tsx
import Button from &quot;@mui/material/Button&quot;;
import Typography from &quot;@mui/material/Typography&quot;;
import Stack from &quot;@mui/material/Stack&quot;;
import ThemeToggle from &quot;@/components/theme-toggle&quot;;

export default function HomePage() {
  return (
    &amp;lt;Stack spacing={3} sx={{ p: 4 }}&amp;gt;
      &amp;lt;Stack direction=&quot;row&quot; sx={{ justifyContent: &quot;center&quot;, alignItems: &quot;center&quot; }}&amp;gt;
        &amp;lt;Typography variant=&quot;h4&quot;&amp;gt;MUI + Next.js 16&amp;lt;/Typography&amp;gt;
        &amp;lt;ThemeToggle /&amp;gt;
      &amp;lt;/Stack&amp;gt;
      &amp;lt;Typography variant=&quot;body1&quot;&amp;gt;
        This button, color, and font are all coming from your custom theme.
      &amp;lt;/Typography&amp;gt;
      &amp;lt;Button variant=&quot;contained&quot; color=&quot;primary&quot;&amp;gt;
        Themed Button
      &amp;lt;/Button&amp;gt;
    &amp;lt;/Stack&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Refresh the page a few times with dark mode on — there should be &lt;strong&gt;zero&lt;/strong&gt; flash of the wrong theme, and the button color should match your custom palette instead of MUI's default blue.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;app/page.tsx&lt;/code&gt; does not need &quot;use client&quot; just because it renders &lt;code&gt;ThemeToggle&lt;/code&gt;. Next.js allows Server Components to render Client Components; only the interactive subtree needs the client boundary.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;frequently-asked-questions&quot;&gt;Frequently Asked Questions&lt;/h3&gt;
&lt;h4 id=&quot;do-i-need-mui-material-nextjs-if-i-m-only-using-the-pages-router-&quot;&gt;Do I need &lt;code&gt;@mui/material-nextjs&lt;/code&gt; if I'm only using the Pages Router?&lt;/h4&gt;
&lt;p&gt;The Next.js integration package supports both routers. For the Pages Router, use the versioned &lt;code&gt;pagesRouter&lt;/code&gt; entry point that matches your Next.js version, such as &lt;code&gt;v13-pagesRouter&lt;/code&gt; for Next.js 13. The setup differs from the App Router and uses &lt;code&gt;_document.tsx&lt;/code&gt;/&lt;code&gt;_app.tsx&lt;/code&gt; rather than the App Router cache-provider setup.&lt;/p&gt;
&lt;h4 id=&quot;why-does-my-theme-file-need-use-client-if-themeprovider-is-used-in-a-server-component-layout-&quot;&gt;Why does my theme file need &lt;code&gt;&quot;use client&quot;&lt;/code&gt; if &lt;code&gt;ThemeProvider&lt;/code&gt; is used in a Server Component layout?&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;ThemeProvider&lt;/code&gt; itself is already a Client Component internally, so it's safe to render from a Server Component layout. The &lt;code&gt;&quot;use client&quot;&lt;/code&gt; on the theme file is a safety measure — &lt;code&gt;createTheme()&lt;/code&gt; returns a rich object with methods, and keeping its creation explicitly client-side avoids subtle serialization issues as your theme grows more complex.&lt;/p&gt;
&lt;h4 id=&quot;why-can-t-i-just-check-theme-palette-mode-dark-in-my-styles-&quot;&gt;Why can't I just check &lt;code&gt;theme.palette.mode === &quot;dark&quot;&lt;/code&gt; in my styles?&lt;/h4&gt;
&lt;p&gt;You can, but only if you don't care about the SSR flicker — MUI's own docs say directly that any &lt;code&gt;theme.palette.mode&lt;/code&gt; check will flash the wrong mode on first load when CSS variables are enabled, because the server has no way to know the client's saved preference at render time. &lt;code&gt;theme.applyStyles(&quot;dark&quot;, {...})&lt;/code&gt; avoids this entirely by generating mode-scoped CSS instead of picking a value during render. See &lt;a rel=&quot;nofollow&quot; href=&quot;#write-mode-aware-styles-the-right-way&quot;&gt;Step 6&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;can-i-use-mui-with-tailwind-css-in-the-same-next-js-project-&quot;&gt;Can I use MUI with Tailwind CSS in the same Next.js project?&lt;/h4&gt;
&lt;p&gt;Yes. The important issue is CSS cascade and style precedence rather than class-name collisions. MUI's &lt;code&gt;enableCssLayer&lt;/code&gt; option places generated MUI styles inside an &lt;code&gt;@layer mui&lt;/code&gt; layer, making it easier to control how MUI styles interact with Tailwind CSS, CSS Modules, and other global styles.&lt;/p&gt;
&lt;h4 id=&quot;my-styles-show-up-correctly-but-flash-unstyled-for-a-split-second-what-s-wrong-&quot;&gt;My styles show up correctly but flash unstyled for a split second — what's wrong?&lt;/h4&gt;
&lt;p&gt;Almost always means &lt;code&gt;AppRouterCacheProvider&lt;/code&gt; isn't wrapping your whole app, or it's nested in the wrong place (it needs to be a direct wrapper inside &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, around everything else). Double-check Step 2's structure.&lt;/p&gt;
&lt;h4 id=&quot;do-i-still-need-cssbaseline-&quot;&gt;Do I still need &lt;code&gt;CssBaseline&lt;/code&gt;?&lt;/h4&gt;
&lt;p&gt;No, &lt;code&gt;CssBaseline&lt;/code&gt; isn't technically required. It is commonly recommended because it provides MUI's baseline/reset styles and applies theme-aware background and text defaults.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h3&gt;
&lt;p&gt;You now have a complete, flash-free MUI setup in Next.js 16 — the official &lt;code&gt;AppRouterCacheProvider&lt;/code&gt; handling SSR streaming correctly (using the Next.js 16-matched &lt;code&gt;v16-appRouter&lt;/code&gt; entry point), a typed custom theme with your own colors and &lt;code&gt;next/font&lt;/code&gt;-optimized typography, a working light/dark toggle powered by MUI's native CSS variables, and mode-aware component styles written with &lt;code&gt;theme.applyStyles()&lt;/code&gt; instead of a flicker-prone &lt;code&gt;palette.mode&lt;/code&gt; check. This is the same foundation you'd want under a real dashboard, admin panel, or SaaS product.&lt;/p&gt;
&lt;p&gt;From here, a natural next step is exploring MUI's &lt;code&gt;styled()&lt;/code&gt; API and &lt;code&gt;sx&lt;/code&gt; prop for component-level customization, or building out a full design system with typed theme augmentation (&lt;code&gt;declare module &quot;@mui/material/styles&quot;&lt;/code&gt;) for custom palette keys.&lt;/p&gt;
&lt;h3 id=&quot;continue-learning&quot;&gt;Continue Learning&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/server-vs-client-components-nextjs-16&quot;&gt;Server vs Client Components in Next.js 16 (2026 Guide)&lt;/a&gt; — go deeper on the Server/Client boundary this guide's &lt;code&gt;&quot;use client&quot;&lt;/code&gt; theme file relies on.&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/nextjs-16-metadata&quot;&gt;How to Add Metadata in Next.js 16 (2026 Beginner's Guide)&lt;/a&gt; — pair this theme setup with proper per-page titles, Open Graph tags, and SEO metadata.&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/nextjs-rendering-strategies-explained&quot;&gt;Next.js Rendering Strategies Explained: SSR, SSG, CSR, ISR &amp;amp; Cache Components (2026)&lt;/a&gt; — understand the streaming/hydration model that makes &lt;code&gt;InitColorSchemeScript&lt;/code&gt; necessary in the first place.&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/saas-starter-architecture-nextjs&quot;&gt;SaaS Starter Architecture with Next.js 2026&lt;/a&gt; — see where a themed component library like this one fits into a larger app structure.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;📦 &lt;strong&gt;Source Code:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Muhammad-Ali-sma/mui-theme-setup-nextjs-16&quot;&gt;View on GitHub&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28671/mui-v9-theme-setup-in-next-js-16-full-2026-guide</guid>
<pubDate>Sat, 26 Sep 2026 03:07:57 +0000</pubDate>
</item>
</channel>
</rss>