样式库互操作性
虽然您可以使用 Material UI 提供的基于 Emotion 的样式解决方案,但您也可以使用您已经熟悉的方案,从纯 CSS 到 styled-components。
本指南旨在记录最流行的替代方案,但您应该发现此处应用的原则可以适用于其他库。以下是一些样式解决方案的示例
纯 CSS
没什么特别的,只是纯 CSS。
.slider {
color: #20b2aa;
}
.slider:hover {
color: #2e8b57;
}
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 注入顺序 ⚠️
注意: 大多数 CSS-in-JS 解决方案将其样式注入到 HTML <head>
的底部,这使得 Material UI 优先于您的自定义样式。为了消除对 !important 的需求,您需要更改 CSS 注入顺序。这是一个演示如何在 Material UI 中完成此操作的示例
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>
);
}
注意: 如果您正在使用 Emotion 并在您的应用程序中使用了自定义缓存,那么该缓存将覆盖来自 Material UI 的缓存。为了使注入顺序仍然正确,您需要添加 prepend 选项。这是一个示例
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>
);
}
注意: 如果您正在使用 styled-components 并且具有带有自定义 target
的 StyleSheetManager
,请确保 target 是 HTML <head>
中的第一个元素。如果您想了解如何完成此操作,可以查看 @mui/styled-engine-sc
包中的 StyledEngineProvider
实现。
更深层的元素
如果您尝试样式化 Slider,您可能需要影响 Slider 的一些子元素,例如 thumb。在 Material UI 中,所有子元素的特异性都增加了 2:.parent .child {}
。在编写覆盖时,您需要执行相同的操作。
以下示例除了 slider 本身的自定义样式外,还覆盖了 slider 的 thumb
样式。
.slider {
color: #20b2aa;
}
.slider:hover {
color: #2e8b57;
}
.slider .MuiSlider-thumb {
border-radius: 1px;
}
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>
);
}
上面的演示依赖于默认的 className
值,但您可以使用 slotProps
API 提供您自己的类名。
.slider {
color: #20b2aa;
}
.slider:hover {
color: #2e8b57;
}
.slider .thumb {
border-radius: 1px;
}
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>
);
}
全局 CSS
显式地为组件提供类名太麻烦了吗? 您可以定位 Material UI 生成的类名。
.MuiSlider-root {
color: #20b2aa;
}
.MuiSlider-root:hover {
color: #2e8b57;
}
import * as React from 'react';
import Slider from '@mui/material/Slider';
import './GlobalCssSlider.css';
export default function GlobalCssSlider() {
return <Slider defaultValue={30} />;
}
CSS 注入顺序 ⚠️
注意: 大多数 CSS-in-JS 解决方案将其样式注入到 HTML <head>
的底部,这使得 Material UI 优先于您的自定义样式。为了消除对 !important 的需求,您需要更改 CSS 注入顺序。这是一个演示如何在 Material UI 中完成此操作的示例
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>
);
}
注意: 如果您正在使用 Emotion 并在您的应用程序中使用了自定义缓存,那么该缓存将覆盖来自 Material UI 的缓存。为了使注入顺序仍然正确,您需要添加 prepend 选项。这是一个示例
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>
);
}
注意: 如果您正在使用 styled-components 并且具有带有自定义 target
的 StyleSheetManager
,请确保 target 是 HTML <head>
中的第一个元素。如果您想了解如何完成此操作,可以查看 @mui/styled-engine-sc
包中的 StyledEngineProvider
实现。
更深层的元素
如果您尝试样式化 Slider,您可能需要影响 Slider 的一些子元素,例如 thumb。在 Material UI 中,所有子元素的特异性都增加了 2:.parent .child {}
。在编写覆盖时,您需要执行相同的操作。
以下示例除了 slider 本身的自定义样式外,还覆盖了 slider 的 thumb
样式。
.MuiSlider-root {
color: #20b2aa;
}
.MuiSlider-root:hover {
color: #2e8b57;
}
.MuiSlider-root .MuiSlider-thumb {
border-radius: 1px;
}
import * as React from 'react';
import Slider from '@mui/material/Slider';
import './GlobalCssSliderDeep.css';
export default function GlobalCssSliderDeep() {
return <Slider defaultValue={30} />;
}
Styled Components
更改默认的样式引擎
默认情况下,Material UI 组件使用 Emotion 作为其样式引擎。但是,如果您想使用 styled-components,您可以按照styled-components 指南配置您的应用程序,或从示例项目之一开始
遵循这种方法可以减小捆绑包大小,并消除配置 CSS 注入顺序的需要。
在正确配置样式引擎后,您可以使用来自 @mui/material/styles
的 styled()
实用程序,并直接访问主题。
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} />;
}
更深层的元素
如果您尝试样式化 Slider,您可能需要影响 Slider 的一些子元素,例如 thumb。在 Material UI 中,所有子元素的特异性都增加了 2:.parent .child {}
。在编写覆盖时,您需要执行相同的操作。
以下示例除了 slider 本身的自定义样式外,还覆盖了 slider 的 thumb
样式。
上面的演示依赖于默认的 className
值,但您可以使用 slotProps
API 提供您自己的类名。
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>
);
}
主题
通过使用 Material UI 主题提供器,主题也将在样式引擎(Emotion 或 styled-components,取决于您的配置)的主题上下文中可用。
我们鼓励您在 Material UI 和项目的其余部分之间共享相同的主题对象。
const CustomizedSlider = styled(Slider)(
({ theme }) => `
color: ${theme.palette.primary.main};
:hover {
color: ${darken(theme.palette.primary.main, 0.2)};
}
`,
);
Portals
MUI Base Portal 组件提供了一种将子元素渲染到父组件 DOM 层次结构之外的 DOM 节点中的一流方法。由于 styled-components 作用域其 CSS 的方式,您可能会遇到样式未应用的问题。
例如,如果您尝试样式化 Tooltip 组件生成的 tooltip
,您需要将 className
属性传递给在其 DOM 层次结构之外渲染的元素。以下示例展示了一种解决方法
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;
}
`;
.slider {
color: #20b2aa;
}
.slider:hover {
color: #2e8b57;
}
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 注入顺序 ⚠️
注意: 大多数 CSS-in-JS 解决方案将其样式注入到 HTML <head>
的底部,这使得 Material UI 优先于您的自定义样式。为了消除对 !important 的需求,您需要更改 CSS 注入顺序。这是一个演示如何在 Material UI 中完成此操作的示例
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>
);
}
注意: 如果您正在使用 Emotion 并在您的应用程序中使用了自定义缓存,那么该缓存将覆盖来自 Material UI 的缓存。为了使注入顺序仍然正确,您需要添加 prepend 选项。这是一个示例
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>
);
}
注意: 如果您正在使用 styled-components 并且具有带有自定义 target
的 StyleSheetManager
,请确保 target 是 HTML <head>
中的第一个元素。如果您想了解如何完成此操作,可以查看 @mui/styled-engine-sc
包中的 StyledEngineProvider
实现。
更深层的元素
如果您尝试样式化 Slider,您可能需要影响 Slider 的一些子元素,例如 thumb。在 Material UI 中,所有子元素的特异性都增加了 2:.parent .child {}
。在编写覆盖时,您需要执行相同的操作。重要的是要记住,CSS 模块为每个类添加了唯一的 id,并且该 id 不会出现在 Material UI 提供的子类上。为了摆脱这种情况,CSS 模块提供了一个功能,即 :global
选择器。
以下示例除了 slider 本身的自定义样式外,还覆盖了 slider 的 thumb
样式。
.slider {
color: #20b2aa;
}
.slider:hover {
color: #2e8b57;
}
.slider :global .MuiSlider-thumb {
border-radius: 1px;
}
import * as React from 'react';
// webpack, Parcel or else will inject the CSS into the page
import styles from './CssModulesSliderDeep1.module.css';
import Slider from '@mui/material/Slider';
export default function CssModulesSliderDeep1() {
return (
<div>
<Slider defaultValue={30} />
<Slider defaultValue={30} className={styles.slider} />
</div>
);
}
上面的演示依赖于默认的 className
值,但您可以使用 slotProps
API 提供您自己的类名。
.slider {
color: #20b2aa;
}
.slider:hover {
color: #2e8b57;
}
.slider .thumb {
border-radius: 1px;
}
import * as React from 'react';
// webpack, Parcel or else will inject the CSS into the page
import styles from './CssModulesSliderDeep2.module.css';
import Slider from '@mui/material/Slider';
export default function CssModulesSliderDeep2() {
return (
<div>
<Slider defaultValue={30} />
<Slider
defaultValue={30}
className={styles.slider}
slotProps={{ thumb: { className: styles.thumb } }}
/>
</div>
);
}
Emotion
css
属性
Emotion 的 css()
方法与 Material UI 无缝协作。
主题
它的工作方式与 styled components 完全相同。您可以使用相同的指南。
styled()
API
它的工作方式与 styled components 完全相同。您可以使用相同的指南。
Tailwind CSS
设置
如果您习惯使用 Tailwind CSS 并希望将其与 Material UI 组件一起使用,您可以首先克隆 Tailwind CSS 示例项目。如果您使用不同的框架,或者已经设置了您的项目,请按照以下步骤操作
- 按照 https://tailwind.org.cn/docs/installation/framework-guides 中的说明,将 Tailwind CSS 添加到您的项目中。
- 移除 Tailwind CSS 的 preflight 样式,以便它可以改用 Material UI 的 preflight (CssBaseline)。
module.exports = {
+ corePlugins: {
+ preflight: false,
+ },
};
- 添加
important
选项,使用您的应用程序包装器的 id。例如,Next.js 的#__next
和 CRA 的"#root"
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
+ important: '#root',
theme: {
extend: {},
},
plugins: [],
}
Material UI 使用的大多数 CSS 的特异性为 1,因此这个 important
属性是不必要的。但是,在少数边缘情况下,Material UI 使用嵌套的 CSS 选择器来胜过 Tailwind CSS。使用此步骤有助于确保 更深层的元素 始终可以使用 Tailwind 的实用程序类进行自定义。有关此选项的更多详细信息,请参见 https://tailwind.org.cn/docs/configuration#selector-strategy
- 修复 CSS 注入顺序。大多数 CSS-in-JS 解决方案将其样式注入到 HTML
<head>
的底部,这使得 Material UI 优先于 Tailwind CSS。为了减少对important
属性的需求,您需要更改 CSS 注入顺序。这是一个演示如何在 Material UI 中完成此操作的示例
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>
);
}
注意: 如果您正在使用 Emotion 并在您的应用程序中使用了自定义缓存,它将覆盖来自 Material UI 的缓存。为了使注入顺序仍然正确,您需要添加 prepend 选项。这是一个示例
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>
);
}
注意: 如果您正在使用 styled-components 并且具有带有自定义 target
的 StyleSheetManager
,请确保 target 是 HTML <head>
中的第一个元素。如果您想了解如何完成此操作,可以查看 @mui/styled-engine-sc
包中的 StyledEngineProvider
实现。
- 更改与
Portal
相关的元素的目标容器,以便它们被注入到在步骤 3 中用于在 Tailwind 配置中设置important
选项的主应用程序包装器下。
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>;
);
用法
现在一切都设置好了,您可以开始在 Material UI 组件上使用 Tailwind CSS 了!
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>
);
}
更深层的元素
例如,如果您尝试样式化 Slider,您可能需要自定义其子元素。
此示例演示如何覆盖 Slider 的 thumb
样式。
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>
);
}
样式化伪状态
如果您想样式化组件的伪状态,您可以在 classes
属性中使用适当的键。这是一个示例,说明如何样式化 Slider 的活动状态
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 本身在 Material UI 中不再受支持,但是,如果您喜欢 react-jss
提供的基于 hook 的 API (makeStyles
→ useStyles
),您可以选择 tss-react
。
TSS 与 Material UI 集成良好,并提供比 JSS 更好的 TypeScript 支持。
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'),
);
现在您可以简单地 import { makeStyles, withStyles } from 'tss-react/mui'
。将传递给您的回调函数的主题对象将是您通过 import { useTheme } from '@mui/material/styles'
获取的对象。
如果您想控制 theme
对象应该是什么,您可以从一个名为 makesStyles.ts
的文件中重新导出 makeStyles
和 withStyles
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)
});
然后,库像这样使用
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,
},
},
}));
有关如何设置 SSR 或其他任何信息,请参阅 TSS 文档。