917 lines
28 KiB
Markdown
917 lines
28 KiB
Markdown
|
|
# Style library interoperability
|
|||
|
|
|
|||
|
|
<p class="description">While you can use the Emotion-based styling solution provided by Material UI, you can also use the one you already know, from plain CSS to styled-components.</p>
|
|||
|
|
|
|||
|
|
This guide aims to document the most popular alternatives,
|
|||
|
|
but you should find that the principles applied here can be adapted to other libraries.
|
|||
|
|
There are examples for the following styling solutions:
|
|||
|
|
|
|||
|
|
- [Plain CSS](#plain-css)
|
|||
|
|
- [Global CSS](#global-css)
|
|||
|
|
- [Styled Components](#styled-components)
|
|||
|
|
- [CSS Modules](#css-modules)
|
|||
|
|
- [Emotion](#emotion)
|
|||
|
|
- [Tailwind CSS v3](#tailwind-css-v3)
|
|||
|
|
- [~~JSS~~ TSS](#jss-tss)
|
|||
|
|
|
|||
|
|
## Plain CSS
|
|||
|
|
|
|||
|
|
Nothing fancy, just plain CSS.
|
|||
|
|
|
|||
|
|
{{"demo": "StyledComponents.js", "hideToolbar": true}}
|
|||
|
|
|
|||
|
|
[](https://codesandbox.io/p/sandbox/plain-css-fdue7)
|
|||
|
|
|
|||
|
|
```css title="PlainCssSlider.css"
|
|||
|
|
.slider {
|
|||
|
|
color: #20b2aa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.slider:hover {
|
|||
|
|
color: #2e8b57;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```jsx title="PlainCssSlider.css"
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import Slider from '@mui/material/Slider';
|
|||
|
|
import './PlainCssSlider.css';
|
|||
|
|
|
|||
|
|
export default function PlainCssSlider() {
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<Slider defaultValue={30} />
|
|||
|
|
<Slider defaultValue={30} className="slider" />
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### CSS injection order ⚠️
|
|||
|
|
|
|||
|
|
**Note:** Most CSS-in-JS solutions inject their styles at the bottom of the HTML `<head>`, which gives Material UI precedence over your custom styles. To remove the need for **!important**, you need to change the CSS injection order. Here's a demo of how it can be done in Material UI:
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import { StyledEngineProvider } from '@mui/material/styles';
|
|||
|
|
|
|||
|
|
export default function GlobalCssPriority() {
|
|||
|
|
return (
|
|||
|
|
<StyledEngineProvider injectFirst>
|
|||
|
|
{/* Your component tree. Now you can override Material UI's styles. */}
|
|||
|
|
</StyledEngineProvider>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Note:** If you are using Emotion and have a custom cache in your app, that one will override the one coming from Material UI. In order for the injection order to still be correct, you need to add the prepend option. Here is an example:
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import { CacheProvider } from '@emotion/react';
|
|||
|
|
import createCache from '@emotion/cache';
|
|||
|
|
|
|||
|
|
const cache = createCache({
|
|||
|
|
key: 'css',
|
|||
|
|
prepend: true,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
export default function PlainCssPriority() {
|
|||
|
|
return (
|
|||
|
|
<CacheProvider value={cache}>
|
|||
|
|
{/* Your component tree. Now you can override Material UI's styles. */}
|
|||
|
|
</CacheProvider>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Note:** If you are using styled-components and have `StyleSheetManager` with a custom `target`, make sure that the target is the first element in the HTML `<head>`. If you are curious to see how it can be done, you can take a look on the [`StyledEngineProvider`](https://github.com/mui/material-ui/blob/-/packages/mui-styled-engine-sc/src/StyledEngineProvider/StyledEngineProvider.js) implementation in the `@mui/styled-engine-sc` package.
|
|||
|
|
|
|||
|
|
### Deeper elements
|
|||
|
|
|
|||
|
|
If you attempt to style the Slider,
|
|||
|
|
you will likely need to affect some of the Slider's child elements, for example the thumb.
|
|||
|
|
In Material UI, all child elements have an increased specificity of 2: `.parent .child {}`. When writing overrides, you need to do the same.
|
|||
|
|
|
|||
|
|
The following examples override the slider's `thumb` style in addition to the custom styles on the slider itself.
|
|||
|
|
|
|||
|
|
{{"demo": "StyledComponentsDeep.js", "hideToolbar": true}}
|
|||
|
|
|
|||
|
|
```css title="PlainCssSliderDeep1.css"
|
|||
|
|
.slider {
|
|||
|
|
color: #20b2aa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.slider:hover {
|
|||
|
|
color: #2e8b57;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.slider .MuiSlider-thumb {
|
|||
|
|
border-radius: 1px;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```jsx title="PlainCssSliderDeep1.js"
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import Slider from '@mui/material/Slider';
|
|||
|
|
import './PlainCssSliderDeep1.css';
|
|||
|
|
|
|||
|
|
export default function PlainCssSliderDeep1() {
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<Slider defaultValue={30} />
|
|||
|
|
<Slider defaultValue={30} className="slider" />
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The above demo relies on the [default `className` values](https://v6.mui.com/system/styles/advanced/), but you can provide your own class name with the `slotProps` API.
|
|||
|
|
|
|||
|
|
```css title="PlainCssSliderDeep2.css"
|
|||
|
|
.slider {
|
|||
|
|
color: #20b2aa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.slider:hover {
|
|||
|
|
color: #2e8b57;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.slider .thumb {
|
|||
|
|
border-radius: 1px;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```jsx title="PlainCssSliderDeep2.js"
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import Slider from '@mui/material/Slider';
|
|||
|
|
import './PlainCssSliderDeep2.css';
|
|||
|
|
|
|||
|
|
export default function PlainCssSliderDeep2() {
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<Slider defaultValue={30} />
|
|||
|
|
<Slider
|
|||
|
|
defaultValue={30}
|
|||
|
|
className="slider"
|
|||
|
|
slotProps={{ thumb: { className: 'thumb' } }}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Global CSS
|
|||
|
|
|
|||
|
|
Explicitly providing the class names to the component is too much effort?
|
|||
|
|
[You can target the class names generated by Material UI](https://v6.mui.com/system/styles/advanced/).
|
|||
|
|
|
|||
|
|
[](https://codesandbox.io/p/sandbox/global-classnames-dho8k)
|
|||
|
|
|
|||
|
|
```css title="GlobalCssSlider.css"
|
|||
|
|
.MuiSlider-root {
|
|||
|
|
color: #20b2aa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.MuiSlider-root:hover {
|
|||
|
|
color: #2e8b57;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```jsx title="GlobalCssSlider.js"
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import Slider from '@mui/material/Slider';
|
|||
|
|
import './GlobalCssSlider.css';
|
|||
|
|
|
|||
|
|
export default function GlobalCssSlider() {
|
|||
|
|
return <Slider defaultValue={30} />;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### CSS injection order ⚠️
|
|||
|
|
|
|||
|
|
**Note:** Most CSS-in-JS solutions inject their styles at the bottom of the HTML `<head>`, which gives Material UI precedence over your custom styles. To remove the need for **!important**, you need to change the CSS injection order. Here's a demo of how it can be done in Material UI:
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import { StyledEngineProvider } from '@mui/material/styles';
|
|||
|
|
|
|||
|
|
export default function GlobalCssPriority() {
|
|||
|
|
return (
|
|||
|
|
<StyledEngineProvider injectFirst>
|
|||
|
|
{/* Your component tree. Now you can override Material UI's styles. */}
|
|||
|
|
</StyledEngineProvider>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Note:** If you are using Emotion and have a custom cache in your app, that one will override the one coming from Material UI. In order for the injection order to still be correct, you need to add the prepend option. Here is an example:
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import { CacheProvider } from '@emotion/react';
|
|||
|
|
import createCache from '@emotion/cache';
|
|||
|
|
|
|||
|
|
const cache = createCache({
|
|||
|
|
key: 'css',
|
|||
|
|
prepend: true,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
export default function GlobalCssPriority() {
|
|||
|
|
return (
|
|||
|
|
<CacheProvider value={cache}>
|
|||
|
|
{/* Your component tree. Now you can override Material UI's styles. */}
|
|||
|
|
</CacheProvider>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Note:** If you are using styled-components and have `StyleSheetManager` with a custom `target`, make sure that the target is the first element in the HTML `<head>`. If you are curious to see how it can be done, you can take a look on the [`StyledEngineProvider`](https://github.com/mui/material-ui/blob/-/packages/mui-styled-engine-sc/src/StyledEngineProvider/StyledEngineProvider.js) implementation in the `@mui/styled-engine-sc` package.
|
|||
|
|
|
|||
|
|
### Deeper elements
|
|||
|
|
|
|||
|
|
If you attempt to style the Slider,
|
|||
|
|
you will likely need to affect some of the Slider's child elements, for example the thumb.
|
|||
|
|
In Material UI, all child elements have an increased specificity of 2: `.parent .child {}`. When writing overrides, you need to do the same.
|
|||
|
|
|
|||
|
|
The following example overrides the slider's `thumb` style in addition to the custom styles on the slider itself.
|
|||
|
|
|
|||
|
|
{{"demo": "StyledComponentsDeep.js", "hideToolbar": true}}
|
|||
|
|
|
|||
|
|
```css title="GlobalCssSliderDeep.css"
|
|||
|
|
.MuiSlider-root {
|
|||
|
|
color: #20b2aa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.MuiSlider-root:hover {
|
|||
|
|
color: #2e8b57;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.MuiSlider-root .MuiSlider-thumb {
|
|||
|
|
border-radius: 1px;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```jsx title="GlobalCssSliderDeep.js"
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import Slider from '@mui/material/Slider';
|
|||
|
|
import './GlobalCssSliderDeep.css';
|
|||
|
|
|
|||
|
|
export default function GlobalCssSliderDeep() {
|
|||
|
|
return <Slider defaultValue={30} />;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Styled Components
|
|||
|
|
|
|||
|
|

|
|||
|
|

|
|||
|
|
|
|||
|
|
### Change the default styled engine
|
|||
|
|
|
|||
|
|
By default, Material UI components come with Emotion as their style engine.
|
|||
|
|
If, however, you would like to use styled-components, you can configure your app by following the [styled-components guide](/material-ui/integrations/styled-components/).
|
|||
|
|
|
|||
|
|
Following this approach reduces the bundle size, and removes the need to configure the CSS injection order.
|
|||
|
|
|
|||
|
|
After the style engine is configured properly, you can use the [`styled()`](/system/styled/) utility
|
|||
|
|
from `@mui/material/styles` and have direct access to the theme.
|
|||
|
|
|
|||
|
|
{{"demo": "StyledComponents.js", "hideToolbar": true}}
|
|||
|
|
|
|||
|
|
[](https://codesandbox.io/p/sandbox/styled-components-interoperability-w9z9d)
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import Slider from '@mui/material/Slider';
|
|||
|
|
import { styled } from '@mui/material/styles';
|
|||
|
|
|
|||
|
|
const CustomizedSlider = styled(Slider)`
|
|||
|
|
color: #20b2aa;
|
|||
|
|
|
|||
|
|
:hover {
|
|||
|
|
color: #2e8b57;
|
|||
|
|
}
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
export default function StyledComponents() {
|
|||
|
|
return <CustomizedSlider defaultValue={30} />;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Deeper elements
|
|||
|
|
|
|||
|
|
If you attempt to style the Slider,
|
|||
|
|
you will likely need to affect some of the Slider's child elements, for example the thumb.
|
|||
|
|
In Material UI, all child elements have an increased specificity of 2: `.parent .child {}`. When writing overrides, you need to do the same.
|
|||
|
|
|
|||
|
|
The following examples override the slider's `thumb` style in addition to the custom styles on the slider itself.
|
|||
|
|
|
|||
|
|
{{"demo": "StyledComponentsDeep.js", "defaultCodeOpen": true}}
|
|||
|
|
|
|||
|
|
The above demo relies on the [default `className` values](https://v6.mui.com/system/styles/advanced/), but you can provide your own class name with the `slotProps` API.
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import { styled } from '@mui/material/styles';
|
|||
|
|
import Slider from '@mui/material/Slider';
|
|||
|
|
|
|||
|
|
const CustomizedSlider = styled((props) => (
|
|||
|
|
<Slider slotProps={{ thumb: { className: 'thumb' } }} {...props} />
|
|||
|
|
))`
|
|||
|
|
color: #20b2aa;
|
|||
|
|
|
|||
|
|
:hover {
|
|||
|
|
color: #2e8b57;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
& .thumb {
|
|||
|
|
border-radius: 1px;
|
|||
|
|
}
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
export default function StyledComponentsDeep2() {
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<Slider defaultValue={30} />
|
|||
|
|
<CustomizedSlider defaultValue={30} />
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Theme
|
|||
|
|
|
|||
|
|
By using the Material UI theme provider, the theme will be available in the theme context
|
|||
|
|
of the styled engine too (Emotion or styled-components, depending on your configuration).
|
|||
|
|
|
|||
|
|
:::warning
|
|||
|
|
If you are already using a custom theme with styled-components or Emotion,
|
|||
|
|
it might not be compatible with Material UI's theme specification. If it's not
|
|||
|
|
compatible, you need to render Material UI's ThemeProvider first. This will
|
|||
|
|
ensure the theme structures are isolated. This is ideal for the progressive adoption
|
|||
|
|
of Material UI's components in the codebase.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
You are encouraged to share the same theme object between Material UI and the rest of your project.
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
const CustomizedSlider = styled(Slider)(
|
|||
|
|
({ theme }) => `
|
|||
|
|
color: ${theme.palette.primary.main};
|
|||
|
|
|
|||
|
|
:hover {
|
|||
|
|
color: ${darken(theme.palette.primary.main, 0.2)};
|
|||
|
|
}
|
|||
|
|
`,
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
{{"demo": "StyledComponentsTheme.js"}}
|
|||
|
|
|
|||
|
|
### Portals
|
|||
|
|
|
|||
|
|
The [Portal](/material-ui/react-portal/) component provides a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
|
|||
|
|
Because of the way styled-components scopes its CSS, you may run into issues where styling is not applied.
|
|||
|
|
|
|||
|
|
For example, if you attempt to style the `tooltip` generated by the [Tooltip](/material-ui/react-tooltip/) component,
|
|||
|
|
you will need to pass along the `className` property to the element being rendered outside of it's DOM hierarchy.
|
|||
|
|
The following example shows a workaround:
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import { styled } from '@mui/material/styles';
|
|||
|
|
import Button from '@mui/material/Button';
|
|||
|
|
import Tooltip from '@mui/material/Tooltip';
|
|||
|
|
|
|||
|
|
const StyledTooltip = styled(({ className, ...props }) => (
|
|||
|
|
<Tooltip {...props} classes={{ popper: className }} />
|
|||
|
|
))`
|
|||
|
|
& .MuiTooltip-tooltip {
|
|||
|
|
background: navy;
|
|||
|
|
}
|
|||
|
|
`;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
{{"demo": "StyledComponentsPortal.js"}}
|
|||
|
|
|
|||
|
|
## CSS Modules
|
|||
|
|
|
|||
|
|

|
|||
|
|
|
|||
|
|
It's hard to know the market share of [this styling solution](https://github.com/css-modules/css-modules) as it's dependent on the bundling solution people are using.
|
|||
|
|
|
|||
|
|
{{"demo": "StyledComponents.js", "hideToolbar": true}}
|
|||
|
|
|
|||
|
|
[](https://codesandbox.io/p/sandbox/css-modules-nuyg8)
|
|||
|
|
|
|||
|
|
```css title="CssModulesSlider.module.css"
|
|||
|
|
.slider {
|
|||
|
|
color: #20b2aa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.slider:hover {
|
|||
|
|
color: #2e8b57;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```jsx title="CssModulesSlider.js"
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import Slider from '@mui/material/Slider';
|
|||
|
|
// webpack, Parcel or else will inject the CSS into the page
|
|||
|
|
import styles from './CssModulesSlider.module.css';
|
|||
|
|
|
|||
|
|
export default function CssModulesSlider() {
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<Slider defaultValue={30} />
|
|||
|
|
<Slider defaultValue={30} className={styles.slider} />
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### CSS injection order ⚠️
|
|||
|
|
|
|||
|
|
**Note:** Most CSS-in-JS solutions inject their styles at the bottom of the HTML `<head>`, which gives Material UI precedence over your custom styles. To remove the need for **!important**, you need to change the CSS injection order. Here's a demo of how it can be done in Material UI:
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import { StyledEngineProvider } from '@mui/material/styles';
|
|||
|
|
|
|||
|
|
export default function GlobalCssPriority() {
|
|||
|
|
return (
|
|||
|
|
<StyledEngineProvider injectFirst>
|
|||
|
|
{/* Your component tree. Now you can override Material UI's styles. */}
|
|||
|
|
</StyledEngineProvider>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Note:** If you are using Emotion and have a custom cache in your app, that one will override the one coming from Material UI. To ensure the correct injection order, use the `prepend` option:
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import { CacheProvider } from '@emotion/react';
|
|||
|
|
import createCache from '@emotion/cache';
|
|||
|
|
|
|||
|
|
const cache = createCache({
|
|||
|
|
key: 'css',
|
|||
|
|
prepend: true,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
export default function CssModulesPriority() {
|
|||
|
|
return (
|
|||
|
|
<CacheProvider value={cache}>
|
|||
|
|
{/* Your component tree. Now you can override Material UI's styles. */}
|
|||
|
|
</CacheProvider>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Note:** If you are using styled-components with `StyleSheetManager` and a custom `target`, ensure that the target is the first element in the HTML `<head>`. See the [`StyledEngineProvider`](https://github.com/mui/material-ui/blob/-/packages/mui-styled-engine-sc/src/StyledEngineProvider/StyledEngineProvider.js) implementation in the `@mui/styled-engine-sc` package for an example.
|
|||
|
|
|
|||
|
|
### Deeper elements
|
|||
|
|
|
|||
|
|
When styling a Slider, you may need to target child elements like the thumb. Material UI components often use increased specificity for child elements (for example, `.parent .child`). CSS Modules scopes class names, so the generated class names will not match Material UI's.
|
|||
|
|
|
|||
|
|
To apply styles to Material UI classes from CSS Modules, use the `:global` selector.
|
|||
|
|
|
|||
|
|
#### Using `:global`
|
|||
|
|
|
|||
|
|
```css title="CssModulesSliderDeep1.module.css"
|
|||
|
|
.slider {
|
|||
|
|
color: #20b2aa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.slider:hover {
|
|||
|
|
color: #2e8b57;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.slider :global(.MuiSlider-thumb) {
|
|||
|
|
border-radius: 1px;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```jsx title="CssModulesSliderDeep1.js"
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import Slider from '@mui/material/Slider';
|
|||
|
|
// webpack, Parcel or else will inject the CSS into the page
|
|||
|
|
import styles from './CssModulesSliderDeep1.module.css';
|
|||
|
|
|
|||
|
|
export default function CssModulesSliderDeep1() {
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<Slider defaultValue={30} />
|
|||
|
|
<Slider defaultValue={30} className={styles.slider} />
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Using `slotProps`
|
|||
|
|
|
|||
|
|
```css title="CssModulesSliderDeep2.module.css"
|
|||
|
|
.slider {
|
|||
|
|
color: #20b2aa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.slider:hover {
|
|||
|
|
color: #2e8b57;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.slider .thumb {
|
|||
|
|
border-radius: 1px;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```jsx title="CssModulesSliderDeep2.js"
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import Slider from '@mui/material/Slider';
|
|||
|
|
// webpack, Parcel or else will inject the CSS into the page
|
|||
|
|
import styles from './CssModulesSliderDeep2.module.css';
|
|||
|
|
|
|||
|
|
export default function CssModulesSliderDeep2() {
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<Slider defaultValue={30} />
|
|||
|
|
<Slider
|
|||
|
|
defaultValue={30}
|
|||
|
|
className={styles.slider}
|
|||
|
|
slotProps={{ thumb: { className: styles.thumb } }}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Targeting Material UI state classes with CSS Modules
|
|||
|
|
|
|||
|
|
Material UI uses global class names to indicate component states (for example, `.Mui-selected`, `.Mui-disabled`). Since CSS Modules scope styles locally, targeting these global state classes requires `:global`.
|
|||
|
|
|
|||
|
|
Here's how to apply styles conditionally, based on Material UI state classes:
|
|||
|
|
|
|||
|
|
```css title="MyList.module.css"
|
|||
|
|
.myListItem {
|
|||
|
|
padding: 10px;
|
|||
|
|
border-bottom: 1px solid #ccc;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Combine global state class with a locally scoped class */
|
|||
|
|
:global(.Mui-selected).myListItem {
|
|||
|
|
background-color: #1976d2;
|
|||
|
|
color: white;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```jsx title="CssModulesSliderCustomPseudoClasses.js"
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import List from '@mui/material/List';
|
|||
|
|
import ListItem from '@mui/material/ListItem';
|
|||
|
|
import ListItemText from '@mui/material/ListItemText';
|
|||
|
|
import styles from './MyList.module.css';
|
|||
|
|
|
|||
|
|
export default function MyList() {
|
|||
|
|
return (
|
|||
|
|
<List>
|
|||
|
|
<ListItem className={styles.myListItem} selected>
|
|||
|
|
<ListItemText primary="Selected item" />
|
|||
|
|
</ListItem>
|
|||
|
|
<ListItem className={styles.myListItem}>
|
|||
|
|
<ListItemText primary="Regular item" />
|
|||
|
|
</ListItem>
|
|||
|
|
</List>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This technique allows you to maintain modular styles while responding to dynamic states set by Material UI's global class names.
|
|||
|
|
|
|||
|
|
## Emotion
|
|||
|
|
|
|||
|
|

|
|||
|
|

|
|||
|
|
|
|||
|
|
### The `css` prop
|
|||
|
|
|
|||
|
|
Emotion's `css()` method works seamlessly with Material UI.
|
|||
|
|
|
|||
|
|
{{"demo": "EmotionCSS.js", "defaultCodeOpen": true}}
|
|||
|
|
|
|||
|
|
### Theme
|
|||
|
|
|
|||
|
|
It works exactly like styled components. You can [use the same guide](/material-ui/integrations/interoperability/#styled-components).
|
|||
|
|
|
|||
|
|
### The `styled()` API
|
|||
|
|
|
|||
|
|
It works exactly like styled components. You can [use the same guide](/material-ui/integrations/interoperability/#styled-components).
|
|||
|
|
|
|||
|
|
## Tailwind CSS v3
|
|||
|
|
|
|||
|
|

|
|||
|
|

|
|||
|
|
|
|||
|
|
:::info
|
|||
|
|
For Tailwind CSS v4, please refer to the [v4 integration guide](/material-ui/integrations/tailwindcss/tailwindcss-v4/).
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
### Setup
|
|||
|
|
|
|||
|
|
<!-- #target-branch-reference -->
|
|||
|
|
|
|||
|
|
To use Tailwind CSS with Material UI components, you can start by cloning the [example project](https://github.com/mui/material-ui/tree/master/examples/material-ui-vite-tailwind-ts) built with Vite and TypeScript.
|
|||
|
|
If you use a different framework, or already have set up your project, follow these steps:
|
|||
|
|
|
|||
|
|
1. Add Tailwind CSS to your project, following the instructions in https://v3.tailwindcss.com/docs/installation/framework-guides.
|
|||
|
|
2. Remove [Tailwind CSS's preflight](https://v3.tailwindcss.com/docs/preflight) style so it can use the Material UI's preflight instead ([CssBaseline](/material-ui/react-css-baseline/)).
|
|||
|
|
|
|||
|
|
```diff title="tailwind.config.js"
|
|||
|
|
module.exports = {
|
|||
|
|
+ corePlugins: {
|
|||
|
|
+ preflight: false,
|
|||
|
|
+ },
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. Add the `important` option, using the id of your app wrapper.
|
|||
|
|
- For Next.js projects, use `#__next`. **Note for Next.js 13+ Users (App Router)**: You must now manually add `id="__next"` to your root element (typically `<body>`), as Next.js no longer adds it automatically:
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
<body id="__next">{/* Your app content */}</body>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- For Vite/SPA projects, use `#root` (default in most templates)
|
|||
|
|
|
|||
|
|
```diff title="tailwind.config.js"
|
|||
|
|
module.exports = {
|
|||
|
|
content: [
|
|||
|
|
"./src/**/*.{js,jsx,ts,tsx}",
|
|||
|
|
],
|
|||
|
|
+ important: '#__next', // or '#root'
|
|||
|
|
theme: {
|
|||
|
|
extend: {},
|
|||
|
|
},
|
|||
|
|
plugins: [],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Most of the CSS used by Material UI has a specificity of 1, hence this `important` property is unnecessary.
|
|||
|
|
However, in a few edge cases, Material UI uses nested CSS selectors that win over Tailwind CSS.
|
|||
|
|
Use this step to help ensure that the [deeper elements](#deeper-elements-5) can always be customized using Tailwind's utility classes.
|
|||
|
|
More details on this option can be found here https://v3.tailwindcss.com/docs/configuration#selector-strategy.
|
|||
|
|
|
|||
|
|
4. Fix the CSS injection order. Most CSS-in-JS solutions inject their styles at the bottom of the HTML `<head>`, which gives Material UI precedence over Tailwind CSS. To reduce the need for the `important` property, you need to change the CSS injection order. Here's a demo of how it can be done in Material UI:
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import { StyledEngineProvider } from '@mui/material/styles';
|
|||
|
|
|
|||
|
|
export default function GlobalCssPriority() {
|
|||
|
|
return (
|
|||
|
|
<StyledEngineProvider injectFirst>
|
|||
|
|
{/* Your component tree. Now you can override Material UI's styles. */}
|
|||
|
|
</StyledEngineProvider>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Note:** If you are using Emotion and have a custom cache in your app, it will override the one coming from Material UI. In order for the injection order to still be correct, you need to add the prepend option. Here is an example:
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import { CacheProvider } from '@emotion/react';
|
|||
|
|
import createCache from '@emotion/cache';
|
|||
|
|
|
|||
|
|
const cache = createCache({
|
|||
|
|
key: 'css',
|
|||
|
|
prepend: true,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
export default function PlainCssPriority() {
|
|||
|
|
return (
|
|||
|
|
<CacheProvider value={cache}>
|
|||
|
|
{/* Your component tree. Now you can override Material UI's styles. */}
|
|||
|
|
</CacheProvider>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Note:** If you are using styled-components and have `StyleSheetManager` with a custom `target`, make sure that the target is the first element in the HTML `<head>`. If you are curious to see how it can be done, you can take a look at the [`StyledEngineProvider`](https://github.com/mui/material-ui/blob/-/packages/mui-styled-engine-sc/src/StyledEngineProvider/StyledEngineProvider.js) implementation in the `@mui/styled-engine-sc` package.
|
|||
|
|
|
|||
|
|
5. Change the target container for `Portal`-related elements so that they are injected under the main app wrapper that was used in step 3 for setting up the `important` option in the Tailwind config.
|
|||
|
|
|
|||
|
|
```jsx
|
|||
|
|
// For Next.js:
|
|||
|
|
const rootElement = document.getElementById("__next");
|
|||
|
|
// For Vite/SPA:
|
|||
|
|
// const rootElement = document.getElementById("root");
|
|||
|
|
const root = createRoot(rootElement);
|
|||
|
|
|
|||
|
|
const theme = createTheme({
|
|||
|
|
components: {
|
|||
|
|
MuiPopover: {
|
|||
|
|
defaultProps: {
|
|||
|
|
container: rootElement,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
MuiPopper: {
|
|||
|
|
defaultProps: {
|
|||
|
|
container: rootElement,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
MuiDialog: {
|
|||
|
|
defaultProps: {
|
|||
|
|
container: rootElement,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
MuiModal: {
|
|||
|
|
defaultProps: {
|
|||
|
|
container: rootElement,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
root.render(
|
|||
|
|
<StyledEngineProvider injectFirst>
|
|||
|
|
<ThemeProvider theme={theme}>
|
|||
|
|
<App />
|
|||
|
|
</ThemeProvider>
|
|||
|
|
</StyledEngineProvider>;
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Troubleshooting
|
|||
|
|
|
|||
|
|
If styles aren't applying correctly:
|
|||
|
|
|
|||
|
|
1. Verify your root ID matches the `important` selector in Tailwind config
|
|||
|
|
|
|||
|
|
| Framework | Root Element ID | `important` Selector |
|
|||
|
|
| :-------: | :-------------: | :------------------: |
|
|||
|
|
| Next.js | `id="__next"` | `#__next` |
|
|||
|
|
| Vite/SPA | `id="root"` | `#root` |
|
|||
|
|
|
|||
|
|
2. Check that `preflight: false` is set
|
|||
|
|
3. Ensure `StyledEngineProvider` with `injectFirst` is properly configured
|
|||
|
|
|
|||
|
|
### Usage
|
|||
|
|
|
|||
|
|
Now it's all set up and you can start using Tailwind CSS on the Material UI components!
|
|||
|
|
|
|||
|
|
{{"demo": "StyledComponents.js", "hideToolbar": true}}
|
|||
|
|
|
|||
|
|
[](https://stackblitz.com/edit/github-ndkshy?file=pages%2Findex.tsx)
|
|||
|
|
|
|||
|
|
```jsx title="index.tsx"
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import Slider from '@mui/material/Slider';
|
|||
|
|
|
|||
|
|
export default function App() {
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<Slider defaultValue={30} />
|
|||
|
|
<Slider defaultValue={30} className="text-teal-600" />
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Deeper elements
|
|||
|
|
|
|||
|
|
If you attempt to style the Slider, for example, you'll likely want to customize its child elements.
|
|||
|
|
|
|||
|
|
This example showcases how to override the Slider's `thumb` style.
|
|||
|
|
|
|||
|
|
{{"demo": "StyledComponentsDeep.js", "hideToolbar": true}}
|
|||
|
|
|
|||
|
|
```jsx title="SliderThumbOverrides.tsx"
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import Slider from '@mui/material/Slider';
|
|||
|
|
|
|||
|
|
export default function SliderThumbOverrides() {
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<Slider defaultValue={30} />
|
|||
|
|
<Slider
|
|||
|
|
defaultValue={30}
|
|||
|
|
className="text-teal-600"
|
|||
|
|
slotProps={{ thumb: { className: 'rounded-sm' } }}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Styling pseudo states
|
|||
|
|
|
|||
|
|
If you want to style a component's pseudo-state, you can use the appropriate key in the `classes` prop.
|
|||
|
|
Here is an example of how you can style the Slider's active state:
|
|||
|
|
|
|||
|
|
```jsx title="SliderPseudoStateOverrides.tsx"
|
|||
|
|
import * as React from 'react';
|
|||
|
|
import Slider from '@mui/material/Slider';
|
|||
|
|
|
|||
|
|
export default function SliderThumbOverrides() {
|
|||
|
|
return <Slider defaultValue={30} classes={{ active: 'shadow-none' }} />;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## ~~JSS~~ TSS
|
|||
|
|
|
|||
|
|
[JSS](https://cssinjs.org/) itself is no longer supported in Material UI, however,
|
|||
|
|
if you like the hook-based API (`makeStyles` → `useStyles`) that [`react-jss`](https://codesandbox.io/p/sandbox/j3l06yyqpw) was offering you can opt for [`tss-react`](https://github.com/garronej/tss-react).
|
|||
|
|
|
|||
|
|
[TSS](https://docs.tss-react.dev) integrates well with Material UI and provide a better
|
|||
|
|
TypeScript support than JSS.
|
|||
|
|
|
|||
|
|
:::info
|
|||
|
|
If you are updating from `@material-ui/core` (v4) to `@mui/material` (v5), check out the [tss-react section](/material-ui/migration/migrating-from-jss/#2-use-tss-react) of the Migration guide.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
import { render } from 'react-dom';
|
|||
|
|
import { CacheProvider } from '@emotion/react';
|
|||
|
|
import createCache from '@emotion/cache';
|
|||
|
|
import { ThemeProvider } from '@mui/material/styles';
|
|||
|
|
|
|||
|
|
export const muiCache = createCache({
|
|||
|
|
key: 'mui',
|
|||
|
|
prepend: true,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
//NOTE: Don't use <StyledEngineProvider injectFirst/>
|
|||
|
|
render(
|
|||
|
|
<CacheProvider value={muiCache}>
|
|||
|
|
<ThemeProvider theme={myTheme}>
|
|||
|
|
<Root />
|
|||
|
|
</ThemeProvider>
|
|||
|
|
</CacheProvider>,
|
|||
|
|
document.getElementById('root'),
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Now you can simply
|
|||
|
|
`import { makeStyles, withStyles } from 'tss-react/mui'`.
|
|||
|
|
The theme object that will be passed to your callbacks functions will be the one you
|
|||
|
|
get with
|
|||
|
|
`import { useTheme } from '@mui/material/styles'`.
|
|||
|
|
|
|||
|
|
If you want to take controls over what the `theme` object should be,
|
|||
|
|
you can re-export `makeStyles` and `withStyles` from a file called, for example, `makesStyles.ts`:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
import { useTheme } from '@mui/material/styles';
|
|||
|
|
//WARNING: tss-react require TypeScript v4.4 or newer. If you can't update use:
|
|||
|
|
//import { createMakeAndWithStyles } from "tss-react/compat";
|
|||
|
|
import { createMakeAndWithStyles } from 'tss-react';
|
|||
|
|
|
|||
|
|
export const { makeStyles, withStyles } = createMakeAndWithStyles({
|
|||
|
|
useTheme,
|
|||
|
|
/*
|
|||
|
|
OR, if you have extended the default mui theme adding your own custom properties:
|
|||
|
|
Let's assume the myTheme object that you provide to the <ThemeProvider /> is of
|
|||
|
|
type MyTheme then you'll write:
|
|||
|
|
*/
|
|||
|
|
//"useTheme": useTheme as (()=> MyTheme)
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Then, the library is used like this:
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
import { makeStyles } from 'tss-react/mui';
|
|||
|
|
|
|||
|
|
export function MyComponent(props: Props) {
|
|||
|
|
const { className } = props;
|
|||
|
|
|
|||
|
|
const [color, setColor] = useState<'red' | 'blue'>('red');
|
|||
|
|
|
|||
|
|
const { classes, cx } = useStyles({ color });
|
|||
|
|
|
|||
|
|
//Thanks to cx, className will take priority over classes.root
|
|||
|
|
return <span className={cx(classes.root, className)}>hello world</span>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const useStyles = makeStyles<{ color: 'red' | 'blue' }>()((theme, { color }) => ({
|
|||
|
|
root: {
|
|||
|
|
color,
|
|||
|
|
'&:hover': {
|
|||
|
|
backgroundColor: theme.palette.primary.main,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
}));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
For info on how to setup SSR or anything else, please refer to [the TSS documentation](https://github.com/garronej/tss-react).
|
|||
|
|
|
|||
|
|
:::info
|
|||
|
|
There is [an ESLint plugin](https://docs.tss-react.dev/detecting-unused-classes) for detecting unused classes.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
:::warning
|
|||
|
|
**Keep `@emotion/styled` as a dependency of your project**. Even if you never use it explicitly,
|
|||
|
|
it's a peer dependency of `@mui/material`.
|
|||
|
|
:::
|