Skip to content

[docs] Add InitColorSchemeScript docs and API #45927

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
productId: material-ui
title: InitColorSchemeScript component
components: InitColorSchemeScript
githubSource: packages/mui-material/src/InitColorSchemeScript
---

# InitColorSchemeScript

<p class="description">The InitColorSchemeScript component eliminates dark mode flickering in server-side-rendered applications.</p>

## Introduction

The `InitColorSchemeScript` component is used to remove the dark mode flicker that can occur in server-side-rendered (SSR) applications.
This script runs before React to attach an attribute based on the user preference so that the correct color mode is applied on first render.

For the best user experience, you should implement this component in any server-rendered Material UI app that supports both light and dark modes.

## Basics

First, enable CSS variables with `colorSchemeSelector: 'data'` in your theme.

```js
import { ThemeProvider, createTheme } from '@mui/material/styles';

const theme = createTheme({
cssVariables: {
colorSchemeSelector: 'data',
},
});

function App() {
return <ThemeProvider theme={theme}>{/* Your app */}</ThemeProvider>;
}
```

Then, render the `InitColorSchemeScript` component as the first child of the `<body>` tag.

The sections below detail where to render the `InitColorSchemeScript` component when working with Next.js.

### Next.js App Router

Place the `InitColorSchemeScript` component in the root `layout` file:

```js title="src/app/layout.tsx"
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';

export default function RootLayout(props: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<InitColorSchemeScript />
{props.children}
</body>
</html>
);
}
```

### Next.js Pages Router

Place the `InitColorSchemeScript` component in a custom `_document` file:

```js title="pages/_document.tsx"
import { Html, Head, Main, NextScript } from 'next/document';
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';

export default function MyDocument(props) {
return (
<Html lang="en">
<Head>{/* tags */}</Head>
<body>
<InitColorSchemeScript />
<Main />
<NextScript />
</body>
</Html>
);
}
```

## Customization

### Class attribute

To attach classes to DOM elements, set the `attribute` prop to `"class"`.

```js
<InitColorSchemeScript attribute="class" />
```

This sets the class name on the color scheme node (which defaults to `<html>`) according to the user's system preference.

```html
<html class="dark"></html>
```

### Arbitrary attribute

To attach arbitrary attributes to DOM elements, use `%s` as a placeholder on the `attribute` prop.

```js
<InitColorSchemeScript attribute="[data-theme='%s']" /> // <html data-theme="dark">
<InitColorSchemeScript attribute=".mode-%s" /> // <html class="mode-dark">
```

### Default mode

Set the `defaultMode` prop to specify the default mode when the user first visits the page.

For example, if you want users to see the dark mode on their first visit, set the `defaultMode` prop to `"dark"`.

```js
<InitColorSchemeScript defaultMode="dark" />
```

## Caveats

### Attribute

When customizing the `attribute` prop, make sure to set the `colorSchemeSelector` in the theme to match the attribute you are using.

```js
const theme = createTheme({
cssVariables: {
colorSchemeSelector: 'same value as the `attribute` prop',
},
});
```

### Default mode

When customizing the `defaultMode` prop, make sure to do the same with the `ThemeProvider` component:

```js
<ThemeProvider theme={theme} defaultMode="dark">
```
Original file line number Diff line number Diff line change
Expand Up @@ -26,49 +26,51 @@ createTheme({ cssVariables: { cssVarPrefix: '' } });

To toggle between modes manually, set the `colorSchemeSelector` with one of the following selectors:

<codeblock>

```js class
createTheme({
colorSchemes: { light: true, dark: true },
cssVariables: {
colorSchemeSelector: 'class'
}
});

// CSS Result
.light { ... }
.dark { ... }
```

```js data
createTheme({
colorSchemes: { light: true, dark: true },
cssVariables: {
colorSchemeSelector: 'data'
}
});

// CSS Result
[data-light] { ... }
[data-dark] { ... }
```

```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 { ... }
```

</codeblock>
- `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:

Expand Down Expand Up @@ -162,7 +164,7 @@ If you have such a condition, replace it with the [`theme.applyStyles()` functio
}
```

Next, if you have a custom selector that is **not** `media`, add the `InitColorSchemeScript` component based on the framework that you are using:
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:
Expand Down
4 changes: 4 additions & 0 deletions docs/data/material/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ const pages: MuiPage[] = [
title: 'Click-Away Listener',
},
{ pathname: '/material-ui/react-css-baseline', title: 'CSS Baseline' },
{
pathname: '/material-ui/react-init-color-scheme-script',
title: 'InitColorSchemeScript',
},
{ pathname: '/material-ui/react-modal' },
{ pathname: '/material-ui/react-no-ssr', title: 'No SSR' },
{ pathname: '/material-ui/react-popover' },
Expand Down
1 change: 1 addition & 0 deletions docs/data/material/pagesApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default [
{ pathname: '/material-ui/api/image-list' },
{ pathname: '/material-ui/api/image-list-item' },
{ pathname: '/material-ui/api/image-list-item-bar' },
{ pathname: '/material-ui/api/init-color-scheme-script' },
{ pathname: '/material-ui/api/input' },
{ pathname: '/material-ui/api/input-adornment' },
{ pathname: '/material-ui/api/input-base' },
Expand Down
23 changes: 23 additions & 0 deletions docs/pages/material-ui/api/init-color-scheme-script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from 'react';
import ApiPage from 'docs/src/modules/components/ApiPage';
import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
import jsonPageContent from './init-color-scheme-script.json';

export default function Page(props) {
const { descriptions, pageContent } = props;
return <ApiPage descriptions={descriptions} pageContent={pageContent} />;
}

Page.getInitialProps = () => {
const req = require.context(
'docs/translations/api-docs/init-color-scheme-script',
false,
/\.\/init-color-scheme-script.*.json$/,
);
const descriptions = mapApiPageTranslations(req);

return {
descriptions,
pageContent: jsonPageContent,
};
};
31 changes: 31 additions & 0 deletions docs/pages/material-ui/api/init-color-scheme-script.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"props": {
"attribute": { "type": { "name": "string" }, "default": "'data-mui-color-scheme'" },
"colorSchemeNode": { "type": { "name": "string" }, "default": "'document.documentElement'" },
"colorSchemeStorageKey": { "type": { "name": "string" }, "default": "'mui-color-scheme'" },
"defaultDarkColorScheme": { "type": { "name": "string" }, "default": "'dark'" },
"defaultLightColorScheme": { "type": { "name": "string" }, "default": "'light'" },
"defaultMode": {
"type": {
"name": "enum",
"description": "'dark'<br>&#124;&nbsp;'light'<br>&#124;&nbsp;'system'"
},
"default": "'system'"
},
"modeStorageKey": { "type": { "name": "string" }, "default": "'mui-mode'" },
"nonce": { "type": { "name": "string" } }
},
"name": "InitColorSchemeScript",
"imports": [
"import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';",
"import { InitColorSchemeScript } from '@mui/material';"
],
"classes": [],
"spread": true,
"themeDefaultProps": null,
"muiName": "MuiInitColorSchemeScript",
"filename": "/packages/mui-material/src/InitColorSchemeScript/InitColorSchemeScript.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/material-ui/react-init-color-scheme-script/\">InitColorSchemeScript</a></li></ul>",
"cssComponent": false
}
12 changes: 12 additions & 0 deletions docs/pages/material-ui/react-init-color-scheme-script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocsV2';
import AppFrame from 'docs/src/modules/components/AppFrame';
import * as pageProps from 'docs/data/material/components/init-color-scheme-script/init-color-scheme-script.md?muiMarkdown';

export default function Page() {
return <MarkdownDocs {...pageProps} />;
}

Page.getLayout = (page) => {
return <AppFrame>{page}</AppFrame>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"componentDescription": "",
"propDescriptions": {
"attribute": { "description": "DOM attribute for applying a color scheme." },
"colorSchemeNode": {
"description": "The node (provided as string) used to attach the color-scheme attribute."
},
"colorSchemeStorageKey": {
"description": "localStorage key used to store <code>colorScheme</code>."
},
"defaultDarkColorScheme": {
"description": "The default color scheme to be used in dark mode."
},
"defaultLightColorScheme": {
"description": "The default color scheme to be used in light mode."
},
"defaultMode": {
"description": "The default mode when the storage is empty (user&#39;s first visit)."
},
"modeStorageKey": { "description": "localStorage key used to store <code>mode</code>." },
"nonce": { "description": "Nonce string to pass to the inline script for CSP headers." }
},
"classDescriptions": {}
}
1 change: 1 addition & 0 deletions docs/translations/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"utils": "Utils",
"/material-ui/react-click-away-listener": "Click-Away Listener",
"/material-ui/react-css-baseline": "CSS Baseline",
"/material-ui/react-init-color-scheme-script": "InitColorSchemeScript",
"/material-ui/react-modal": "Modal",
"/material-ui/react-no-ssr": "No SSR",
"/material-ui/react-popover": "Popover",
Expand Down
6 changes: 1 addition & 5 deletions packages/api-docs-builder-core/materialUi/projectSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,7 @@ export const projectSettings: ProjectSettings = {
getComponentInfo: getMaterialUiComponentInfo,
translationLanguages: LANGUAGES,
skipComponent(filename: string) {
return (
filename.match(
/(ThemeProvider|CssVarsProvider|DefaultPropsProvider|InitColorSchemeScript)/,
) !== null
);
return filename.match(/(ThemeProvider|CssVarsProvider|DefaultPropsProvider)/) !== null;
},
translationPagesDirectory: 'docs/translations/api-docs',
generateClassName,
Expand Down
Loading
Loading