# How to Implement Dark Mode in Material-UI Using InitColorSchemeScript

> Implement dark mode in Material-UI seamlessly with InitColorSchemeScript. Prevent FOUC by detecting system preferences and localStorage before React hydrates.

- Repository: [MUI/material-ui](https://github.com/mui/material-ui)
- Tags: how-to-guide
- Published: 2026-02-26

---

**InitColorSchemeScript injects a blocking script into the HTML `<head>` that detects system preferences and reads localStorage before React hydrates, eliminating flash-of-unstyled-content (FOUC) when implementing dark mode in Material-UI.**

The mui/material-ui repository provides a robust solution for theme switching through the `InitColorSchemeScript` component. This utility ensures your application respects user color scheme preferences immediately on page load, preventing visual inconsistencies during hydration. Learning how to implement dark mode in Material-UI using InitColorSchemeScript is essential for creating accessible, performant React applications that respond to OS-level settings.

## What Is InitColorSchemeScript?

`InitColorSchemeScript` is a React component that renders a synchronous script tag designed to execute before your application paints. As implemented in [`packages/mui-material/src/InitColorSchemeScript/InitColorSchemeScript.js`](https://github.com/mui/material-ui/blob/main/packages/mui-material/src/InitColorSchemeScript/InitColorSchemeScript.js), this script performs four critical operations:

- **Detects system preferences** using the browser's `prefers-color-scheme` media query
- **Reads persisted user selections** from `localStorage` under the key `mui-color-scheme`
- **Sets global indicators** by writing to the CSS custom property `--mui-color-scheme` and the HTML element's `data-mui-color-scheme` attribute
- **Blocks rendering** until execution completes, ensuring the initial paint matches the final theme

Because this script runs synchronously in the `<head>`, it prevents the flash of wrong color scheme (FOUC) that typically occurs when theme detection happens after React hydration.

## Why Use InitColorSchemeScript for Dark Mode?

Implementing dark mode without this utility often results in a visible flash of light theme while JavaScript calculates the correct palette. The InitColorSchemeScript approach offers specific advantages:

- **Zero runtime overhead after hydration** – The script executes once during page load, then React takes over state management
- **Universal compatibility** – Works with MUI's `styled` API, the `sx` prop, or external CSS modules through the globally available `--mui-color-scheme` variable
- **Accessibility compliance** – Respects OS-level `prefers-color-scheme` settings unless the user has explicitly chosen a different mode
- **SSR safety** – Prevents hydration mismatches between server-rendered HTML and client-side theme detection

## Step-by-Step Implementation Guide

### Basic Setup with useColorScheme Hook

For applications using Material-UI v5.13 or later, the `useColorScheme` hook provides the most ergonomic integration. This hook, defined in [`packages/mui-material/src/useColorScheme/useColorScheme.js`](https://github.com/mui/material-ui/blob/main/packages/mui-material/src/useColorScheme/useColorScheme.js), automatically synchronizes with the values set by InitColorSchemeScript.

```tsx
import * as React from 'react';
import { ThemeProvider, createTheme, CssBaseline, Switch } from '@mui/material';
import { useColorScheme } from '@mui/material/styles';
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';

export default function App() {
  const { mode, setMode } = useColorScheme();
  
  const theme = React.useMemo(
    () => createTheme({ palette: { mode } }),
    [mode]
  );

  const handleToggle = () => {
    setMode(mode === 'light' ? 'dark' : 'light');
  };

  return (
    <>
      <InitColorSchemeScript />
      <ThemeProvider theme={theme}>
        <CssBaseline />
        <Switch 
          checked={mode === 'dark'} 
          onChange={handleToggle}
          inputProps={{ 'aria-label': 'toggle dark mode' }}
        />
      </ThemeProvider>
    </>
  );
}

```

The `setMode` function automatically updates `localStorage`, dispatches the `mui-color-scheme` event, and modifies the `data-mui-color-scheme` attribute.

### Manual Implementation Without Hooks

If you need custom logic or use an older version, read the attribute directly from the HTML element:

```tsx
import * as React from 'react';
import { ThemeProvider, createTheme, CssBaseline, Switch } from '@mui/material';
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';

export default function App() {
  const prefersDark = React.useMemo(() => 
    document.documentElement.getAttribute('data-mui-color-scheme') === 'dark',
    []
  );
  
  const [mode, setMode] = React.useState<'light' | 'dark'>(
    prefersDark ? 'dark' : 'light'
  );

  const theme = React.useMemo(
    () => createTheme({ palette: { mode } }),
    [mode]
  );

  const toggleColorScheme = () => {
    const next = mode === 'light' ? 'dark' : 'light';
    setMode(next);
    localStorage.setItem('mui-color-scheme', next);
    document.documentElement.setAttribute('data-mui-color-scheme', next);
    window.dispatchEvent(new Event('mui-color-scheme'));
  };

  return (
    <>
      <InitColorSchemeScript />
      <ThemeProvider theme={theme}>
        <CssBaseline />
        <Switch checked={mode === 'dark'} onChange={toggleColorScheme} />
      </ThemeProvider>
    </>
  );
}

```

### Server-Side Rendering with Next.js

For Next.js applications, place `InitColorSchemeScript` in your custom Document component to ensure it renders in the `<head>` before any stylesheets.

**pages/_document.tsx**

```tsx
import Document, { Html, Head, Main, NextScript } from 'next/document';
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head>
          <InitColorSchemeScript nonce={process.env.NEXT_PUBLIC_NONCE} />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

```

**pages/_app.tsx**

```tsx
import { ThemeProvider, createTheme, CssBaseline } from '@mui/material';
import { useColorScheme } from '@mui/material/styles';
import { useEffect } from 'react';

export default function MyApp({ Component, pageProps }) {
  const { mode } = useColorScheme();
  
  const theme = React.useMemo(
    () => createTheme({ 
      palette: { mode },
      cssVariables: true // Enable CSS variables for dynamic switching
    }),
    [mode]
  );

  // Sync body background to prevent flash during navigation
  useEffect(() => {
    document.body.style.backgroundColor = theme.palette.background.default;
  }, [theme]);

  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <Component {...pageProps} />
    </ThemeProvider>
  );
}

```

## Technical Architecture

The implementation relies on coordination between three core files in the mui/material-ui repository:

1. **[`packages/mui-material/src/InitColorSchemeScript/InitColorSchemeScript.js`](https://github.com/mui/material-ui/blob/main/packages/mui-material/src/InitColorSchemeScript/InitColorSchemeScript.js)** – Generates the inline script string that evaluates `localStorage` and `matchMedia('(prefers-color-scheme: dark)')` to determine the effective scheme
2. **[`packages/mui-material/src/useColorScheme/useColorScheme.js`](https://github.com/mui/material-ui/blob/main/packages/mui-material/src/useColorScheme/useColorScheme.js)** – Provides React state management that subscribes to storage events and the custom `mui-color-scheme` event dispatched during manual toggles
3. **[`packages/mui-material/src/createTheme/createTheme.js`](https://github.com/mui/material-ui/blob/main/packages/mui-material/src/createTheme/createTheme.js)** – Consumes the `mode` parameter (`'light'` | `'dark'`) to generate the appropriate palette as defined in [`packages/mui-material/src/styles/cssVars/PaletteCssVars.js`](https://github.com/mui/material-ui/blob/main/packages/mui-material/src/styles/cssVars/PaletteCssVars.js)

The script executes the following logic synchronously:

```javascript
// Simplified representation of the injected script
const saved = localStorage.getItem('mui-color-scheme');
const system = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
const mode = saved || system;

document.documentElement.setAttribute('data-mui-color-scheme', mode);
document.documentElement.style.setProperty('--mui-color-scheme', mode);

```

This execution happens before the browser parses the `<body>`, ensuring CSS variables are available for the first paint.

## Summary

- **Position InitColorSchemeScript first** in your HTML `<head>` to block rendering until the color scheme is determined
- **The script checks `localStorage` key `mui-color-scheme`** and falls back to `prefers-color-scheme` media query detection
- **Use `useColorScheme` hook** (v5.13+) to read and update the mode without manual DOM manipulation
- **For SSR**, include the component in your Document class or root layout to prevent hydration mismatches
- **The CSS variable `--mui-color-scheme`** and attribute `data-mui-color-scheme` provide styling hooks for any CSS-in-JS solution

## Frequently Asked Questions

### Where should I place InitColorSchemeScript in my HTML?

Place it as the first child of your `<head>` element, before any `<link>` tags for stylesheets or other scripts. This positioning ensures the script executes synchronously before the browser begins painting, preventing any flash of incorrect theme colors.

### Does InitColorSchemeScript work with Server-Side Rendering (SSR)?

Yes. When using Next.js or similar frameworks, include `InitColorSchemeScript` in your custom Document component (e.g., [`pages/_document.js`](https://github.com/mui/material-ui/blob/main/pages/_document.js)). The component renders the initialization script into the server-sent HTML, which then executes immediately on the client before React hydration begins, as coordinated with [`packages/mui-material/src/useColorScheme/useColorScheme.js`](https://github.com/mui/material-ui/blob/main/packages/mui-material/src/useColorScheme/useColorScheme.js).

### How does InitColorSchemeScript prevent the flash of wrong color scheme?

The component injects a blocking script that runs synchronously during HTML parsing. By setting the `data-mui-color-scheme` attribute and CSS variables immediately, the browser applies the correct colors during the initial paint rather than waiting for React to hydrate and calculate the theme, effectively eliminating FOUC.

### Can I use InitColorSchemeScript without the useColorScheme hook?

Yes. You can manually read the color scheme from `document.documentElement.getAttribute('data-mui-color-scheme')` after the script runs. To update the theme, modify `localStorage.setItem('mui-color-scheme', mode)` and dispatch `window.dispatchEvent(new Event('mui-color-scheme'))` to notify other components, though the hook handles this synchronization automatically.