CSS theme variables - Configuration
A guide for configuring CSS theme variables in Material UI.
Customizing variable prefix
To change the default variable prefix (--mui), provide a string to cssVarPrefix property, as shown below:
createTheme({ cssVariables: { cssVarPrefix: 'any' } });
// generated stylesheet:
// --any-palette-primary-main: ...;
To remove the prefix, use an empty string as a value:
createTheme({ cssVariables: { cssVarPrefix: '' } });
// generated stylesheet:
// --palette-primary-main: ...;
Toggling dark mode manually
To toggle between modes manually, set the colorSchemeSelector with one of the following selectors:
class: adds a class to the<html>element.createTheme({ colorSchemes: { light: true, dark: true }, cssVariables: { colorSchemeSelector: 'class' } }); // CSS Result .light { ... } .dark { ... }data: adds a data attribute to the<html>element.createTheme({ colorSchemes: { light: true, dark: true }, cssVariables: { colorSchemeSelector: 'data' } }); // CSS Result [data-light] { ... } [data-dark] { ... }string: adds a custom selector to the<html>element.// The value must start with dot (.) for class or square brackets ([]) for data createTheme({ colorSchemes: { light: true, dark: true }, cssVariables: { colorSchemeSelector: '.theme-%s' } }); // CSS Result .theme-light { ... } .theme-dark { ... }
Then, use useColorScheme hook to switch between modes:
import { useColorScheme } from '@mui/material/styles';
function ModeSwitcher() {
const { mode, setMode } = useColorScheme();
if (!mode) {
return null;
}
return (
<select
value={mode}
onChange={(event) => {
setMode(event.target.value);
// For TypeScript, cast `event.target.value as 'light' | 'dark' | 'system'`:
}}
>
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
);
}
Determining the system mode
To determine if the system mode is light or dark, use the systemMode property:
const { mode, systemMode } = useColorScheme();
console.log(mode); // 'system'
console.log(systemMode); // 'light' | 'dark'
However, if the mode is not system, the systemMode will be undefined.
const { mode, systemMode } = useColorScheme();
console.log(mode); // 'light' | 'dark'
console.log(systemMode); // undefined
Preventing SSR flickering
For SSR (server-side rendering) applications, Material UI cannot detect the user-selected mode on the server, causing the screen to flicker from light to dark during the hydration phase on the client.
To prevent the issue, you need to ensure that there is no usage of theme.palette.mode === 'dark' in your code base.
If you have such a condition, replace it with the theme.applyStyles() function:
import Card from '@mui/material/Card';
function App() {
return (
<Card
- sx={(theme) => ({
- backgroundColor: theme.palette.mode === 'dark' ? '#000' : '#fff',
- '&:hover': {
- backgroundColor: theme.palette.mode === 'dark' ? '#333' : '#f5f5f5',
- },
- })}
+ sx={[
+ {
+ backgroundColor: '#fff',
+ '&:hover': {
+ backgroundColor: '#f5f5f5',
+ },
+ },
+ (theme) =>
+ theme.applyStyles('dark', {
+ backgroundColor: '#000',
+ '&:hover': {
+ backgroundColor: '#333',
+ },
+ }),
+ ]}
/>
);
}
Next, if you have a custom selector that is not media, add the InitColorSchemeScript component based on the framework that you are using:
Next.js App Router
Add the following code to the root layout file:
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';
export default function RootLayout(props) {
return (
<html lang="en" suppressHydrationWarning>
<body>
{/* must come before the <main> element */}
<InitColorSchemeScript attribute="class" />
<main>{children}</main>
</body>
</html>
);
}
Next.js Pages Router
Add the following code to the custom pages/_document.js file:
import Document, { Html, Head, Main, NextScript } from 'next/document';
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';
export default class MyDocument extends Document {
render() {
return (
<Html>
<Head>...</Head>
<body>
{/* must come before the <Main> element */}
<InitColorSchemeScript attribute="class" />
<Main />
<NextScript />
</body>
</Html>
);
}
}
Gatsby
Place the script in your gatsby-ssr.js file:
import * as React from 'react';
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';
export function onRenderBody({ setPreBodyComponents }) {
setPreBodyComponents([<InitColorSchemeScript attribute="class" />]);
}
Forcing a specific color scheme
To force a specific color scheme for some part of your application, set the selector to the component or HTML element directly.
In the example below, all the components inside the div will always be dark:
// if the selector is '.mode-%s'
<div className=".mode-dark">
<Paper sx={{ p: 2 }}>
<TextField label="Email" type="email" margin="normal" />
<TextField label="Password" type="password" margin="normal" />
<Button>Sign in</Button>
</Paper>
{/* other components */}
</div>Nested and multiple theme providers
A ThemeProvider writes the CSS theme variables to :root, so they apply to the whole page.
A nested or scoped provider writes them on an element that wraps its children instead, so they only apply there.
Nested providers
A nested ThemeProvider whose variables differ from the outer theme wraps its children in a div with display: contents and a generated class.
The class only sets the variables that differ, and it follows the outer light or dark mode:
<ThemeProvider theme={appTheme}>
<Button>Uses appTheme</Button>
<ThemeProvider theme={promoTheme}>
<Button>Uses promoTheme</Button>
</ThemeProvider>
</ThemeProvider>
:root {
--mui-palette-primary-main: #1976d2;
}
.mui-theme-1x2y3z {
--mui-palette-primary-main: #9c27b0;
}
A nested provider with the same variables, for example one that only changes components defaults, renders no element.
Dialogs, menus, popovers, tooltips, and other components that render in a portal apply the class to their own root, so they use the nested theme too.
For elements that other libraries portal, use the useThemeScope() hook to get the class:
import { useThemeScope } from '@mui/material/styles';
function Toast() {
const themeScope = useThemeScope();
return ReactDOM.createPortal(<div {...themeScope}>…</div>, document.body);
}
To put the class on your own element instead of the div, for example inside a table or a list, where a div isn't valid HTML, use the component prop:
<ThemeProvider theme={promoTheme} component="tbody" className="promo">
{rows}
</ThemeProvider>
Sibling providers
Two ThemeProviders that aren't nested in each other both write to :root and overwrite each other, which logs a warning in development.
Add the scoped prop to the one that should only apply to its children, like an embedded widget:
<ThemeProvider theme={appTheme}>
<App />
</ThemeProvider>
<ThemeProvider theme={widgetTheme} scoped="widget">
<Widget />
</ThemeProvider>
A scoped provider writes all of its variables on its element, and has its own light or dark mode.
When scoped is a string, it's added to the class name and to the storage keys of the mode and color scheme, for example mui-mode-widget.
Disabling CSS color scheme
By default, createTheme() attaches a CSS color-scheme property based on the palette mode.
You can disable this by setting disableCssColorScheme to true:
createTheme({
cssVariables: { disableCssColorScheme: true },
});
The generated CSS will not include the color-scheme property:
@media (prefers-color-scheme: dark) {
:root {
- color-scheme: dark;
--mui-palette-primary-main: #90caf9;
...
}
}
Instant transition between color schemes
To disable CSS transitions when switching between modes, apply the disableTransitionOnChange prop:
<ThemeProvider disableTransitionOnChange />
Force theme recalculation between modes
By default, the ThemeProvider does not re-render when switching between light and dark modes when cssVariables: true is set in the theme.
If you want to opt-out from this behavior, use the forceThemeRerender prop in the ThemeProvider:
<ThemeProvider forceThemeRerender />