I am using MUI in my Next.js web app to switch between system, light, and dark mode. I am having it persist between sessions by saving the theme selected in local storage. I have placed the ability to change the theme of the web app within a dropdown in the settings modal, so changing of the theme occurs through a useContext
. The issue I’m encountering is that theming is not persistent across all of my components if the theme the user selects is either the “system” theme (if your system them is dark mode), or “dark” theme. In addition, I also receive this error if my theme is not “light” theme on initial load or when switching from “light” theme to one of the others:
Warning: Prop `className` did not match. Server: "MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit MuiIconButton-edgeStart MuiIconButton-sizeMedium css-134qg7o-MuiButtonBase-root-MuiIconButton-root" Client: "MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit MuiIconButton-edgeStart MuiIconButton-sizeMedium css-6pxnsq-MuiButtonBase-root-MuiIconButton-root
After some searching, I originally thought it was because I had not been using CssBaseline
within the ThemeProvider
tags, however, after adding it, it appears to have only made things worse, and the error persists. I will show the different behavior in screenshots below:
Expected behavior if in “system” or “dark” mode (This is without CssBaseline
between the ThemeProvider
tags:
The actual behavior without CssBaseline
on load in with “system” or “dark” mode:
The behavior on load when in “system” or “dark” mode with CssBaseline
:
The behavior when switching from “light” mode to “system” or “dark” mode:
Listed below is my context code and how I’m getting the theme from the system, how I’m storing it in local storage, and how I’m switching it:
const ThemeContext = createContext()
const useThemeContext = () => useContext(ThemeContext)
export const ThemeModeProviderComponent = ({ children }) => {
const systemTheme = typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
const [selectedTheme, setSelectedTheme] = useState(() =>
typeof localStorage !== 'undefined' && localStorage.getItem('theme') || 'system'
)
const themes = {
light: createTheme({
palette: {
mode: 'light',
primary: {
main: '#0065bd'
},
secondary: {
main: '#00b6d3'
}
}
}),
dark: createTheme({
palette: {
mode: 'dark',
primary: {
main: '#0065bd'
},
secondary: {
main: '#00b6d3'
}
}
}),
system: createTheme({
palette: {
mode: systemTheme,
primary: {
main: '#0065bd'
},
secondary: {
main: '#00b6d3'
}
}
})
}
useEffect(() => {
if (selectedTheme === 'system') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
themes.system.palette.mode = systemTheme
}
}, [selectedTheme, themes.system])
const handleThemeChange = (event) => {
const { value } = event.target
setSelectedTheme(value)
localStorage.setItem('theme', value)
}
return (
<ThemeContext.Provider value={{ selectedTheme, handleThemeChange }}>
<ThemeProvider theme={themes[selectedTheme]}>
<CssBaseline enableColorScheme/>
{children}
</ThemeProvider>
</ThemeContext.Provider>
)
}
export const ThemeSelector = () => {
const { selectedTheme, handleThemeChange } = useThemeContext()
if (typeof window === 'undefined') return null
return (
<FormControl>
<Select
value={selectedTheme}
onChange={handleThemeChange}
>
<MenuItem value='system'>System</MenuItem>
<MenuItem value='dark'>Dark</MenuItem>
<MenuItem value='light'>Light</MenuItem>
</Select>
</FormControl>
)
}
Another possibility is that, because of the way I’m handling the showing and hiding of the drawer, it could be affecting the style. I basically copied the persistent drawer example from the MUI website here, but I don’t think it’s the case, as, as we’ve seen in the images, it it working properly to an extent.