material-ui icon indicating copy to clipboard operation
material-ui copied to clipboard

[MUI v6] Setting default mode flickers the light/dark theme at first render

Open MartinXPN opened this issue 1 year ago • 5 comments

Steps to reproduce

Link to the initial issue: https://github.com/mui/material-ui/issues/43622

Steps:

  1. Set your system (OS) default theme to dark
  2. Set the default MUI theme mode to light
  3. Open the page in incognito mode
  4. The page first renders with the dark mode and then changes to light, causing flickering

Make sure to re-open the page in incognito mode or change the modeStorageKey. Otherwise, the key will be stored and you won't notice any flickering.

Here is a deployed example: https://profound.academy

Current behavior

If your system default mode is dark, and you set your website default theme to be light, the very first render is dark, which then switches to light, causing flickering. That's pretty unpleasant for the visitors.

Expected behavior

It should render using the defaultMode right away without any flickering.

Context

No response

Your environment

npx @mui/envinfo

Using Chrome

  System:
    OS: macOS 14.6.1
  Binaries:
    Node: 20.10.0 - /opt/homebrew/opt/node@20/bin/node
    npm: 10.2.3 - /opt/homebrew/opt/node@20/bin/npm
    pnpm: Not Found
  Browsers:
    Chrome: 129.0.6668.71
    Edge: Not Found
    Safari: 17.6
  npmPackages:
    @emotion/react: ^11.13.3 => 11.13.3 
    @emotion/styled: ^11.13.0 => 11.13.0 
    @mui/core-downloads-tracker:  6.1.2 
    @mui/icons-material: ^6.1.2 => 6.1.2 
    @mui/material: ^6.1.2 => 6.1.2 
    @mui/private-theming:  6.1.2 
    @mui/styled-engine:  6.1.2 
    @mui/system:  6.1.2 
    @mui/types:  7.2.17 
    @mui/utils:  5.16.6 
    @mui/x-charts: ^7.18.0 => 7.18.0 
    @mui/x-charts-vendor:  7.18.0 
    @mui/x-data-grid: ^7.18.0 => 7.18.0 
    @mui/x-date-pickers: ^7.18.0 => 7.18.0 
    @mui/x-internals:  7.18.0 
    @mui/x-tree-view: ^7.18.0 => 7.18.0 
    @types/react: ^18.3.10 => 18.3.10 
    react: ^18.3.1 => 18.3.1 
    react-dom: ^18.3.1 => 18.3.1 
    styled-components:  5.3.11 
    typescript: ^5.6.2 => 5.6.2 

Search keywords: default mode, flickering, MUI v6

MartinXPN avatar Oct 03 '24 09:10 MartinXPN

@aarongarciah do you know why this might be happening?

MartinXPN avatar Oct 16 '24 20:10 MartinXPN

Can you provide a code sample that you are using for the ThemeProvider? I tested locally with a fresh Next.js project and it works fine.

// theme.js
const theme = createTheme({
  cssVariables: {
    colorSchemeSelector: "class",
  },
  colorSchemes: {
    light: true,
    dark: true,
  },
  typography: {
    fontFamily: roboto.style.fontFamily,
  },
});

// layout.tsx
export default function RootLayout(props: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <AppRouterCacheProvider options={{ enableCssLayer: true }}>
          <ThemeProvider defaultMode="light" theme={theme}>
            {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
            <CssBaseline />
            {props.children}
          </ThemeProvider>
        </AppRouterCacheProvider>
      </body>
    </html>
  );
}

siriwatknp avatar Oct 17 '24 03:10 siriwatknp

Sure, I think you're missing the InitColorSchemeScript. If I remove it the flickering goes away on the first render, but the whole app flickers on all the next renders if I switch to dark mode and refresh the page.

Here is the ThemeProvider.tsx:

'use client';

import {memo, ReactNode} from "react";
import {Roboto} from "next/font/google";
import {createTheme, ThemeProvider as MuiThemeProvider} from "@mui/material/styles";
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';
import CssBaseline from "@mui/material/CssBaseline";
import ColorModeContextProvider from "@/theme/ColorModeContext";
import {AppRouterCacheProvider} from "@mui/material-nextjs/v14-appRouter";


const font = Roboto({weight: ['300', '400', '500', '700'], subsets: ['latin']});

export const theme = createTheme({
    cssVariables: {colorSchemeSelector: 'class'},
    defaultColorScheme: 'light',
    typography: {
        fontFamily: font.style.fontFamily,
        h1: {fontSize: 42, fontWeight: 'bold'},
        h2: {fontSize: 30},
        h3: {fontSize: 26},
        h4: {fontSize: 20},
        h5: {fontSize: 18},
        h6: {fontSize: 16},
    },
    colorSchemes: {
        light: {
            palette: {
                background: {default: '#ffffff', paper: '#ffffff'},
                primary: {main: '#212b36'},
                secondary: {main: '#fa541c'},
                info: {main: '#f44336'},
                success: {main: '#4caf50'},
                error: {main: '#f44336'},

                // @ts-ignore
                console: {main: '#e0e0e0'},
                secondaryAction: {main: '#000000'},
                neutral: {main: '#ffffff', contrastText: '#212121'},
                unavailable: {main: '#363636'},
            },
        },
        dark: {
            palette: {
                background: {default: '#171717', paper: '#212121'},
                primary: {main: '#ffffff'},
                secondary: {main: '#fa541c'},
                info: {main: '#f44336'},
                success: {main: '#388e3c'},
                error: {main: '#d32f2f'},

                // @ts-ignore
                console: {main: '#212121'},
                secondaryAction: {main: '#fff'},
                neutral: {main: '#212121', contrastText: '#fff'},
                unavailable: {main: '#797979'},
            },
        },
    },
});


function ThemeProvider({children}: {children: ReactNode}) {
    return <>
        <AppRouterCacheProvider options={{ enableCssLayer: true }}>
        <InitColorSchemeScript modeStorageKey="theme-mode" attribute="class" />
        <MuiThemeProvider modeStorageKey="theme-mode" defaultMode="light" theme={theme}>
        <ColorModeContextProvider>
            <CssBaseline />
            {children}
        </ColorModeContextProvider>
        </MuiThemeProvider>
        </AppRouterCacheProvider>
    </>
}

export default memo(ThemeProvider);

And here is the ColorModeContext.tsx:

import {createContext, memo, ReactNode} from "react";
import {useColorScheme} from "@mui/material/styles";

export type PaletteMode = 'light' | 'dark' | 'system';

interface ColorModeContextProps {
    mode: PaletteMode;
    setMode: (mode: PaletteMode) => void;
}

export const ColorModeContext = createContext<ColorModeContextProps>({
    mode: 'light',
    setMode: (_: PaletteMode) => {},
});

function ColorModeContextProvider({children}: {children: ReactNode}) {
    const {mode, setMode} = useColorScheme();

    return <ColorModeContext.Provider value={{mode: mode ?? 'light', setMode: setMode}}>
        {children}
    </ColorModeContext.Provider>
}

export default memo(ColorModeContextProvider);

When I open the app in incognito mode (fresh window every time), it opens up in dark mode and stays in dark mode for 1-2 seconds. After which, it switches to light mode.

MartinXPN avatar Oct 17 '24 06:10 MartinXPN

Sure, I think you're missing the InitColorSchemeScript. If I remove it the flickering goes away on the first render, but the whole app flickers on all the next renders if I switch to dark mode and refresh the page.

Gotcha, you are right. The problem is the InitColorSchemeScript. At the beginning, the default mode is system (no local storage yet).

I will open a PR to fix this. You will need to provide the defaultMode to InitColorSchemeScript in the future too.

siriwatknp avatar Oct 17 '24 07:10 siriwatknp

Sounds good, I think it would be pretty straightforward from the users' perspective (I assume that in most projects ThemeProvider and InitColorSchemeScript are located in the same file, so keeping them in sync shouldn't be a problem).

MartinXPN avatar Oct 17 '24 07:10 MartinXPN

This issue has been closed. If you have a similar problem but not exactly the same, please open a new issue. Now, if you have additional information related to this issue or things that could help future readers, feel free to leave a comment.

[!NOTE] @MartinXPN How did we do? Your experience with our support team matters to us. If you have a moment, please share your thoughts in this short Support Satisfaction survey.

github-actions[bot] avatar Oct 21 '24 02:10 github-actions[bot]