樣式庫互操作性
雖然您可以使用 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 並且有 StyleSheetManager
與自訂 target
,請確保目標是 HTML <head>
中的第一個元素。如果您好奇想了解如何完成此操作,您可以查看 @mui/styled-engine-sc
套件中 StyledEngineProvider
的實作。
更深層的元素
如果您嘗試為 Slider 設定樣式,您可能需要影響 Slider 的某些子元素,例如拇指。在 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 並且有 StyleSheetManager
與自訂 target
,請確保目標是 HTML <head>
中的第一個元素。如果您好奇想了解如何完成此操作,您可以查看 @mui/styled-engine-sc
套件中 StyledEngineProvider
的實作。
更深層的元素
如果您嘗試為 Slider 設定樣式,您可能需要影響 Slider 的某些子元素,例如拇指。在 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 的某些子元素,例如拇指。在 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)};
}
`,
);
入口
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 並且有 StyleSheetManager
與自訂 target
,請確保目標是 HTML <head>
中的第一個元素。如果您好奇想了解如何完成此操作,您可以查看 @mui/styled-engine-sc
套件中 StyledEngineProvider
的實作。
更深層的元素
如果您嘗試為 Slider 設定樣式,您可能需要影響 Slider 的某些子元素,例如拇指。在 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 範例專案開始。如果您使用不同的框架,或已經設定了您的專案,請按照以下步驟操作
- 將 Tailwind CSS 新增到您的專案,請按照 https://tailwind.dev.org.tw/docs/installation/framework-guides 中的指示操作。
- 移除 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.dev.org.tw/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 並且有 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 的 active 狀態設定樣式的範例
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 文件。