322 lines
8.0 KiB
Markdown
322 lines
8.0 KiB
Markdown
|
|
# CSS theme variables - Configuration
|
|||
|
|
|
|||
|
|
<p class="description">A guide for configuring CSS theme variables in Material UI.</p>
|
|||
|
|
|
|||
|
|
## Customizing variable prefix
|
|||
|
|
|
|||
|
|
To change the default variable prefix (`--mui`), provide a string to `cssVarPrefix` property, as shown below:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
createTheme({ cssVariables: { cssVarPrefix: 'any' } });
|
|||
|
|
|
|||
|
|
// generated stylesheet:
|
|||
|
|
// --any-palette-primary-main: ...;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
To remove the prefix, use an empty string as a value:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
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.
|
|||
|
|
|
|||
|
|
```js class
|
|||
|
|
createTheme({
|
|||
|
|
colorSchemes: { light: true, dark: true },
|
|||
|
|
cssVariables: {
|
|||
|
|
colorSchemeSelector: 'class'
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// CSS Result
|
|||
|
|
.light { ... }
|
|||
|
|
.dark { ... }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- `data`: adds a data attribute to the `<html>` element.
|
|||
|
|
|
|||
|
|
```js data
|
|||
|
|
createTheme({
|
|||
|
|
colorSchemes: { light: true, dark: true },
|
|||
|
|
cssVariables: {
|
|||
|
|
colorSchemeSelector: 'data'
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// CSS Result
|
|||
|
|
[data-light] { ... }
|
|||
|
|
[data-dark] { ... }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- `string`: adds a custom selector to the `<html>` element.
|
|||
|
|
|
|||
|
|
```js string
|
|||
|
|
// 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:
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
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>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
:::success
|
|||
|
|
After React hydrates the tree, the mode is set to `system` to follow the user's preference.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
### Determining the system mode
|
|||
|
|
|
|||
|
|
To determine if the system mode is `light` or `dark`, use the `systemMode` property:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
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`.
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
const { mode, systemMode } = useColorScheme();
|
|||
|
|
|
|||
|
|
console.log(mode); // 'light' | 'dark'
|
|||
|
|
console.log(systemMode); // undefined
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Preventing SSR flickering
|
|||
|
|
|
|||
|
|
For SSR (server-side rendering) applications, Material UI can not detected 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](/material-ui/customization/dark-mode/#styling-in-dark-mode):
|
|||
|
|
|
|||
|
|
```diff
|
|||
|
|
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`](/material-ui/react-init-color-scheme-script/) component based on the framework that you are using:
|
|||
|
|
|
|||
|
|
:::success
|
|||
|
|
The `attribute` has to be the same as the one you set in the `colorSchemeSelector` property:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
createTheme({
|
|||
|
|
cssVariables: {
|
|||
|
|
colorSchemeSelector: 'class'
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
<InitColorSchemeScript attribute="class" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
### Next.js App Router
|
|||
|
|
|
|||
|
|
Add the following code to the [root layout](https://nextjs.org/docs/app/api-reference/file-conventions/layout#root-layouts) file:
|
|||
|
|
|
|||
|
|
```jsx title="app/layout.js"
|
|||
|
|
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>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
:::warning
|
|||
|
|
If you don't add `suppressHydrationWarning` to your `<html>` tag, you will see warnings about `"Extra attributes from the server"` because `InitColorSchemeScript` updates that element.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
### Next.js Pages Router
|
|||
|
|
|
|||
|
|
Add the following code to the custom [`pages/_document.js`](https://nextjs.org/docs/pages/building-your-application/routing/custom-document) file:
|
|||
|
|
|
|||
|
|
```jsx title="pages/_document.js"
|
|||
|
|
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`](https://www.gatsbyjs.com/docs/reference/config-files/gatsby-ssr/) file:
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
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:
|
|||
|
|
|
|||
|
|
<codeblock>
|
|||
|
|
|
|||
|
|
```js class
|
|||
|
|
// 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>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```js data-attribute
|
|||
|
|
// if the selector is '[data-mode-%s]'
|
|||
|
|
<div data-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>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
</codeblock>
|
|||
|
|
|
|||
|
|
## Disabling CSS color scheme
|
|||
|
|
|
|||
|
|
By default, `createTheme()` attaches a [CSS `color-scheme` property](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/color-scheme) based on the palette mode.
|
|||
|
|
You can disable this by setting `disableCssColorScheme` to `true`:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
createTheme({
|
|||
|
|
cssVariables: { disableCssColorScheme: true },
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The generated CSS will not include the `color-scheme` property:
|
|||
|
|
|
|||
|
|
```diff
|
|||
|
|
@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:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
<ThemeProvider disableTransitionOnChange />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
{{"demo": "DisableTransitionOnChange.js"}}
|
|||
|
|
|
|||
|
|
## 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:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
<ThemeProvider forceThemeRerender />
|
|||
|
|
```
|