Skip to content

Commit f32c76c

Browse files
authored
[docs] Add InitColorSchemeScript docs and API (#45927)
1 parent adb0a76 commit f32c76c

File tree

14 files changed

+413
-53
lines changed

14 files changed

+413
-53
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
---
2+
productId: material-ui
3+
title: InitColorSchemeScript component
4+
components: InitColorSchemeScript
5+
githubSource: packages/mui-material/src/InitColorSchemeScript
6+
---
7+
8+
# InitColorSchemeScript
9+
10+
<p class="description">The InitColorSchemeScript component eliminates dark mode flickering in server-side-rendered applications.</p>
11+
12+
## Introduction
13+
14+
The `InitColorSchemeScript` component is used to remove the dark mode flicker that can occur in server-side-rendered (SSR) applications.
15+
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.
16+
17+
For the best user experience, you should implement this component in any server-rendered Material UI app that supports both light and dark modes.
18+
19+
## Basics
20+
21+
First, enable CSS variables with `colorSchemeSelector: 'data'` in your theme.
22+
23+
```js
24+
import { ThemeProvider, createTheme } from '@mui/material/styles';
25+
26+
const theme = createTheme({
27+
cssVariables: {
28+
colorSchemeSelector: 'data',
29+
},
30+
});
31+
32+
function App() {
33+
return <ThemeProvider theme={theme}>{/* Your app */}</ThemeProvider>;
34+
}
35+
```
36+
37+
Then, render the `InitColorSchemeScript` component as the first child of the `<body>` tag.
38+
39+
The sections below detail where to render the `InitColorSchemeScript` component when working with Next.js.
40+
41+
### Next.js App Router
42+
43+
Place the `InitColorSchemeScript` component in the root `layout` file:
44+
45+
```js title="src/app/layout.tsx"
46+
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';
47+
48+
export default function RootLayout(props: { children: React.ReactNode }) {
49+
return (
50+
<html lang="en" suppressHydrationWarning>
51+
<body>
52+
<InitColorSchemeScript />
53+
{props.children}
54+
</body>
55+
</html>
56+
);
57+
}
58+
```
59+
60+
### Next.js Pages Router
61+
62+
Place the `InitColorSchemeScript` component in a custom `_document` file:
63+
64+
```js title="pages/_document.tsx"
65+
import { Html, Head, Main, NextScript } from 'next/document';
66+
import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';
67+
68+
export default function MyDocument(props) {
69+
return (
70+
<Html lang="en">
71+
<Head>{/* tags */}</Head>
72+
<body>
73+
<InitColorSchemeScript />
74+
<Main />
75+
<NextScript />
76+
</body>
77+
</Html>
78+
);
79+
}
80+
```
81+
82+
## Customization
83+
84+
### Class attribute
85+
86+
To attach classes to DOM elements, set the `attribute` prop to `"class"`.
87+
88+
```js
89+
<InitColorSchemeScript attribute="class" />
90+
```
91+
92+
This sets the class name on the color scheme node (which defaults to `<html>`) according to the user's system preference.
93+
94+
```html
95+
<html class="dark"></html>
96+
```
97+
98+
### Arbitrary attribute
99+
100+
To attach arbitrary attributes to DOM elements, use `%s` as a placeholder on the `attribute` prop.
101+
102+
```js
103+
<InitColorSchemeScript attribute="[data-theme='%s']" /> // <html data-theme="dark">
104+
<InitColorSchemeScript attribute=".mode-%s" /> // <html class="mode-dark">
105+
```
106+
107+
### Default mode
108+
109+
Set the `defaultMode` prop to specify the default mode when the user first visits the page.
110+
111+
For example, if you want users to see the dark mode on their first visit, set the `defaultMode` prop to `"dark"`.
112+
113+
```js
114+
<InitColorSchemeScript defaultMode="dark" />
115+
```
116+
117+
## Caveats
118+
119+
### Attribute
120+
121+
When customizing the `attribute` prop, make sure to set the `colorSchemeSelector` in the theme to match the attribute you are using.
122+
123+
```js
124+
const theme = createTheme({
125+
cssVariables: {
126+
colorSchemeSelector: 'same value as the `attribute` prop',
127+
},
128+
});
129+
```
130+
131+
### Default mode
132+
133+
When customizing the `defaultMode` prop, make sure to do the same with the `ThemeProvider` component:
134+
135+
```js
136+
<ThemeProvider theme={theme} defaultMode="dark">
137+
```

docs/data/material/customization/css-theme-variables/configuration.md

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -26,49 +26,51 @@ createTheme({ cssVariables: { cssVarPrefix: '' } });
2626

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

29-
<codeblock>
30-
31-
```js class
32-
createTheme({
33-
colorSchemes: { light: true, dark: true },
34-
cssVariables: {
35-
colorSchemeSelector: 'class'
36-
}
37-
});
38-
39-
// CSS Result
40-
.light { ... }
41-
.dark { ... }
42-
```
43-
44-
```js data
45-
createTheme({
46-
colorSchemes: { light: true, dark: true },
47-
cssVariables: {
48-
colorSchemeSelector: 'data'
49-
}
50-
});
51-
52-
// CSS Result
53-
[data-light] { ... }
54-
[data-dark] { ... }
55-
```
56-
57-
```js string
58-
// The value must start with dot (.) for class or square brackets ([]) for data
59-
createTheme({
60-
colorSchemes: { light: true, dark: true },
61-
cssVariables: {
62-
colorSchemeSelector: '.theme-%s'
63-
}
64-
});
65-
66-
// CSS Result
67-
.theme-light { ... }
68-
.theme-dark { ... }
69-
```
70-
71-
</codeblock>
29+
- `class`: adds a class to the `<html>` element.
30+
31+
```js class
32+
createTheme({
33+
colorSchemes: { light: true, dark: true },
34+
cssVariables: {
35+
colorSchemeSelector: 'class'
36+
}
37+
});
38+
39+
// CSS Result
40+
.light { ... }
41+
.dark { ... }
42+
```
43+
44+
- `data`: adds a data attribute to the `<html>` element.
45+
46+
```js data
47+
createTheme({
48+
colorSchemes: { light: true, dark: true },
49+
cssVariables: {
50+
colorSchemeSelector: 'data'
51+
}
52+
});
53+
54+
// CSS Result
55+
[data-light] { ... }
56+
[data-dark] { ... }
57+
```
58+
59+
- `string`: adds a custom selector to the `<html>` element.
60+
61+
```js string
62+
// The value must start with dot (.) for class or square brackets ([]) for data
63+
createTheme({
64+
colorSchemes: { light: true, dark: true },
65+
cssVariables: {
66+
colorSchemeSelector: '.theme-%s'
67+
}
68+
});
69+
70+
// CSS Result
71+
.theme-light { ... }
72+
.theme-dark { ... }
73+
```
7274

7375
Then, use `useColorScheme` hook to switch between modes:
7476

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

165-
Next, if you have a custom selector that is **not** `media`, add the `InitColorSchemeScript` component based on the framework that you are using:
167+
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:
166168

167169
:::success
168170
The `attribute` has to be the same as the one you set in the `colorSchemeSelector` property:

docs/data/material/pages.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ const pages: MuiPage[] = [
121121
title: 'Click-Away Listener',
122122
},
123123
{ pathname: '/material-ui/react-css-baseline', title: 'CSS Baseline' },
124+
{
125+
pathname: '/material-ui/react-init-color-scheme-script',
126+
title: 'InitColorSchemeScript',
127+
},
124128
{ pathname: '/material-ui/react-modal' },
125129
{ pathname: '/material-ui/react-no-ssr', title: 'No SSR' },
126130
{ pathname: '/material-ui/react-popover' },

docs/data/material/pagesApi.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export default [
5555
{ pathname: '/material-ui/api/image-list' },
5656
{ pathname: '/material-ui/api/image-list-item' },
5757
{ pathname: '/material-ui/api/image-list-item-bar' },
58+
{ pathname: '/material-ui/api/init-color-scheme-script' },
5859
{ pathname: '/material-ui/api/input' },
5960
{ pathname: '/material-ui/api/input-adornment' },
6061
{ pathname: '/material-ui/api/input-base' },
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as React from 'react';
2+
import ApiPage from 'docs/src/modules/components/ApiPage';
3+
import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
4+
import jsonPageContent from './init-color-scheme-script.json';
5+
6+
export default function Page(props) {
7+
const { descriptions, pageContent } = props;
8+
return <ApiPage descriptions={descriptions} pageContent={pageContent} />;
9+
}
10+
11+
Page.getInitialProps = () => {
12+
const req = require.context(
13+
'docs/translations/api-docs/init-color-scheme-script',
14+
false,
15+
/\.\/init-color-scheme-script.*.json$/,
16+
);
17+
const descriptions = mapApiPageTranslations(req);
18+
19+
return {
20+
descriptions,
21+
pageContent: jsonPageContent,
22+
};
23+
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"props": {
3+
"attribute": { "type": { "name": "string" }, "default": "'data-mui-color-scheme'" },
4+
"colorSchemeNode": { "type": { "name": "string" }, "default": "'document.documentElement'" },
5+
"colorSchemeStorageKey": { "type": { "name": "string" }, "default": "'mui-color-scheme'" },
6+
"defaultDarkColorScheme": { "type": { "name": "string" }, "default": "'dark'" },
7+
"defaultLightColorScheme": { "type": { "name": "string" }, "default": "'light'" },
8+
"defaultMode": {
9+
"type": {
10+
"name": "enum",
11+
"description": "'dark'<br>&#124;&nbsp;'light'<br>&#124;&nbsp;'system'"
12+
},
13+
"default": "'system'"
14+
},
15+
"modeStorageKey": { "type": { "name": "string" }, "default": "'mui-mode'" },
16+
"nonce": { "type": { "name": "string" } }
17+
},
18+
"name": "InitColorSchemeScript",
19+
"imports": [
20+
"import InitColorSchemeScript from '@mui/material/InitColorSchemeScript';",
21+
"import { InitColorSchemeScript } from '@mui/material';"
22+
],
23+
"classes": [],
24+
"spread": true,
25+
"themeDefaultProps": null,
26+
"muiName": "MuiInitColorSchemeScript",
27+
"filename": "/packages/mui-material/src/InitColorSchemeScript/InitColorSchemeScript.tsx",
28+
"inheritance": null,
29+
"demos": "<ul><li><a href=\"/material-ui/react-init-color-scheme-script/\">InitColorSchemeScript</a></li></ul>",
30+
"cssComponent": false
31+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as React from 'react';
2+
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocsV2';
3+
import AppFrame from 'docs/src/modules/components/AppFrame';
4+
import * as pageProps from 'docs/data/material/components/init-color-scheme-script/init-color-scheme-script.md?muiMarkdown';
5+
6+
export default function Page() {
7+
return <MarkdownDocs {...pageProps} />;
8+
}
9+
10+
Page.getLayout = (page) => {
11+
return <AppFrame>{page}</AppFrame>;
12+
};
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"componentDescription": "",
3+
"propDescriptions": {
4+
"attribute": { "description": "DOM attribute for applying a color scheme." },
5+
"colorSchemeNode": {
6+
"description": "The node (provided as string) used to attach the color-scheme attribute."
7+
},
8+
"colorSchemeStorageKey": {
9+
"description": "localStorage key used to store <code>colorScheme</code>."
10+
},
11+
"defaultDarkColorScheme": {
12+
"description": "The default color scheme to be used in dark mode."
13+
},
14+
"defaultLightColorScheme": {
15+
"description": "The default color scheme to be used in light mode."
16+
},
17+
"defaultMode": {
18+
"description": "The default mode when the storage is empty (user&#39;s first visit)."
19+
},
20+
"modeStorageKey": { "description": "localStorage key used to store <code>mode</code>." },
21+
"nonce": { "description": "Nonce string to pass to the inline script for CSP headers." }
22+
},
23+
"classDescriptions": {}
24+
}

docs/translations/translations.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
"utils": "Utils",
107107
"/material-ui/react-click-away-listener": "Click-Away Listener",
108108
"/material-ui/react-css-baseline": "CSS Baseline",
109+
"/material-ui/react-init-color-scheme-script": "InitColorSchemeScript",
109110
"/material-ui/react-modal": "Modal",
110111
"/material-ui/react-no-ssr": "No SSR",
111112
"/material-ui/react-popover": "Popover",

packages/api-docs-builder-core/materialUi/projectSettings.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,7 @@ export const projectSettings: ProjectSettings = {
4242
getComponentInfo: getMaterialUiComponentInfo,
4343
translationLanguages: LANGUAGES,
4444
skipComponent(filename: string) {
45-
return (
46-
filename.match(
47-
/(ThemeProvider|CssVarsProvider|DefaultPropsProvider|InitColorSchemeScript)/,
48-
) !== null
49-
);
45+
return filename.match(/(ThemeProvider|CssVarsProvider|DefaultPropsProvider)/) !== null;
5046
},
5147
translationPagesDirectory: 'docs/translations/api-docs',
5248
generateClassName,

0 commit comments

Comments
 (0)