MUI 主題 (Theme) 完整指南:從基礎到進階
本文內容基於 MUI v5.x 撰寫,同時也適用於 MUI v6.x。MUI v6 在主題系統上與 v5 保持高度相容,主要差異在於 v6 將 variants 從 components 移至 styleOverrides 內。如果你使用的是 v4 或更早版本,部分 API 可能有所不同,建議參考對應版本的官方文件。
前言
在開發 React 應用程式時,維持一致的視覺風格是一個常見的挑戰。當專案規模變大,散落在各處的顏色值、字體設定、間距數值會讓維護變得困難。MUI(Material-UI)的主題系統正是為了解決這個問題而設計的——它讓我們能夠在一個地方集中定義所有的視覺規範,並確保整個應用程式都遵循這些規範。
這篇文章整理了 MUI Theme 的核心概念與實用技巧,從最基礎的 API 使用到進階的客製化方法,希望能幫助你快速掌握 MUI 主題系統的使用方式。
本篇文章的內容主要來自 MUI 官方網站的 Theming 相關文件。你可以將這篇文章作為官方文件的快速導讀,快速了 解想要實作的功能以及大致使用方法。若需要進一步深入了解,可以透過各段落提供的連結找到對應的官方文件。
快速開始
先來看一個簡單的範例,讓我們對主題的應用有一個直觀的了解:
import React from 'react';
import { createTheme, ThemeProvider, Button } from '@mui/material';
const theme = createTheme({
palette: {
primary: {
main: '#2196f3',
},
},
});
const App = () => {
return (
<ThemeProvider theme={theme}>
<Button variant="contained" color="primary">
Click me!
</Button>
</ThemeProvider>
);
};
export default App;
這個範例展示了 MUI 主題的三個核心步驟:
- 建立主題:使用
createTheme定義自訂的視覺規範 - 注入主題:使用
ThemeProvider將主題傳遞給子元件 - 使用主題:MUI 元件會自動套用主題中定義的樣式
接下來,讓我們深入了解每個核心 API 的使用方式。
核心 API
MUI 主題系統的核心由三個 API 組成:createTheme、ThemeProvider 和 useTheme。理解這三者的關係是掌握 MUI 主題的第一步。
| API | 用途 | 使用位置 |
|---|---|---|
createTheme | 建立主題配置物件 | 通常在獨立的 theme 檔案中 |
ThemeProvider | 將主題注入 React Context | 應用程式的根元件或需要不同主題的區塊 |
useTheme | 在元件中存取主題變數 | 任何需要讀取主題值的元件 |
createTheme
createTheme 是建立主題的核心函數,它接受一個配置物件作為參數,包含調色板(palette)、字體(typography)、間距(spacing)等設定:
import { createTheme } from '@mui/material';
const theme = createTheme({
palette: {
primary: {
main: '#2196f3',
},
secondary: {
main: '#f50057',
},
},
typography: {
fontFamily: 'Arial, sans-serif',
fontSize: 14,
},
spacing: 8,
});
如果需要合併多個主題配置,可以使用 deepmerge 工具:
import { deepmerge } from '@mui/utils';
import { createTheme } from '@mui/material/styles';
const theme = createTheme(deepmerge(baseTheme, customTheme));
ThemeProvider
ThemeProvider 利用 React Context 將主題傳遞給所有子元件。通常我們會在應用程式的根元件使用它:
import React from 'react';
import { ThemeProvider } from '@mui/material';
import App from './App';
import theme from './theme';
const Root = () => {
return (
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
);
};
export default Root;
我們也可以嵌套多個 ThemeProvider,內層的 theme 會覆蓋外層的 theme。這在需要局部使用不同主題時非常有用:
<ThemeProvider theme={outerTheme}>
<Checkbox defaultChecked /> {/* 使用 outerTheme */}
<ThemeProvider theme={innerTheme}>
<Checkbox defaultChecked /> {/* 使用 innerTheme */}
</ThemeProvider>
</ThemeProvider>
useTheme Hook
當你需要在元件中動態存取主題變數時,可以使用 useTheme hook:
import { useTheme } from '@mui/material/styles';
function PriceTag({ price }) {
const theme = useTheme();
return (
<span style={{
color: theme.palette.primary.main,
padding: theme.spacing(1, 2),
}}>
${price}
</span>
);
}
主題配置變數總覽
MUI 主題包含多個配置區塊,每個區塊負責不同的視覺面向:
| 配置區塊 | 用途 | 常見設定 |
|---|---|---|
| palette | 調色板 | primary、secondary、error、背景色 |
| typography | 字體排版 | fontFamily、fontSize、各級標題樣式 |
| spacing | 間距 | 基礎間距單位(預設 8px) |
| breakpoints | 響應式斷點 | xs、sm、md、lg、xl 的寬度值 |
| zIndex | 層級 | modal、drawer、tooltip 等的 z-index |
| transitions | 過渡動畫 | duration、easing 函數 |
| components | 元件樣式 | 各元件的 defaultProps 和 styleOverrides |
自訂主題變數
除了內建的配置區塊,你也可以新增自訂變數。這在需要定義專案特有的設計 token 時非常有用:
const theme = createTheme({
status: {
danger: orange[500],
},
customShadows: {
card: '0 4px 6px rgba(0, 0, 0, 0.1)',
},
});
如果使用 TypeScript,需要透過 module augmentation 擴展型別定義:
declare module '@mui/material/styles' {
interface Theme {
status: {
danger: string;
};
customShadows: {
card: string;
};
}
interface ThemeOptions {
status?: {
danger?: string;
};
customShadows?: {
card?: string;
};
}
}
Palette 調色板
Palette 是 MUI 主題中最常被客製化的部分,它定義了應用程式中使用的所有顏色。
預設顏色種類
MUI 預設提供六種語意化的顏色種類,每種都有其特定用途:
| 顏色種類 | 用途 | 預設色系 |
|---|---|---|
primary | 主要操作、品牌色 | 藍色 |
secondary | 次要操作、輔助色 | 紫色 |
error | 錯誤狀態、刪除操作 | 紅色 |
warning | 警告訊息 | 橘色 |
info | 資訊提示 | 淺藍色 |
success | 成功狀態 | 綠色 |
每個顏色種類都包含以下屬性:
interface PaletteColor {
light?: string; // 淺色變體
main: string; // 主要顏色(必填)
dark?: string; // 深色變體
contrastText?: string; // 對比文字顏色
}
三種設定顏色的方式
方式一:使用 MUI 內建顏色
最簡單的方式是直接引入 MUI 提供的 color 物件,它會自動包含 light、main、dark 等變體:
import { createTheme } from '@mui/material/styles';
import { blue, pink } from '@mui/material/colors';
const theme = createTheme({
palette: {
primary: blue,
secondary: pink,
},
});
方式二:只指定 main 顏色
如果只提供 main 顏色,MUI 會自動計算 light、dark 和 contrastText:
const theme = createTheme({
palette: {
primary: {
main: '#2196f3',
},
},
});
方式三:完整自訂所有顏色
如果需要精確控制每個顏色變體:
const theme = createTheme({
palette: {
primary: {
light: '#64b5f6',
main: '#2196f3',
dark: '#1976d2',
contrastText: '#fff',
},
},
});
新增自訂顏色種類
除了預設的六種顏色,你也可以新增專案特有的顏色種類:
const theme = createTheme({
palette: {
neutral: {
main: '#64748B',
contrastText: '#fff',
},
brand: {
main: '#FF6B35',
light: '#FF8F66',
dark: '#CC5529',
},
},
});
新增自訂顏色時,需要擴展 Palette 和 PaletteOptions 介面:
declare module '@mui/material/styles' {
interface Palette {
neutral: Palette['primary'];
brand: Palette['primary'];
}
interface PaletteOptions {
neutral?: PaletteOptions['primary'];
brand?: PaletteOptions['primary'];
}
}
如果要在 Button 等元件上使用自訂顏色,還需要擴展對應元件的 props:
declare module '@mui/material/Button' {
interface ButtonPropsColorOverrides {
neutral: true;
brand: true;
}
}
Dark Mode 深色模式
Dark Mode 已經成為現代應用程式的標配功能。MUI 提供了完整的 Dark Mode 支援,從簡單的靜態設定到動態切換都能輕鬆實現。
基本設定
最簡單的方式是在 palette.mode 中指定 'dark':
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
const darkTheme = createTheme({
palette: {
mode: 'dark',
},
});
function App() {
return (
<ThemeProvider theme={darkTheme}>
<CssBaseline /> {/* 重要:會自動調整背景色和文字顏色 */}
<main>This app is using the dark mode</main>
</ThemeProvider>
);
}
<CssBaseline /> 會根據當前的 palette.mode 自動設定適當的背景色和文字顏色。在 Dark Mode 下,它會將背景設為深色、文字設為淺色。建議在使用 Dark Mode 時一定要加上這個元件。
動態切換 Light/Dark Mode
實際應用中,我們通常需要讓使用者能夠切換主題。以下是一個完整的實作範例:
import React, { createContext, useContext, useMemo, useState } from 'react';
import { ThemeProvider, createTheme, PaletteMode } from '@mui/material';
import { amber, deepOrange, grey } from '@mui/material/colors';
// 1. 建立 Context 來管理切換功能
const ColorModeContext = createContext({ toggleColorMode: () => {} });
// 2. 根據 mode 產生不同的主題配置
const getDesignTokens = (mode: PaletteMode) => ({
palette: {
mode,
...(mode === 'light'
? {
primary: amber,
background: { default: '#fafafa', paper: '#fff' },
text: { primary: grey[900], secondary: grey[700] },
}
: {
primary: deepOrange,
background: { default: '#121212', paper: '#1e1e1e' },
text: { primary: '#fff', secondary: grey[400] },
}),
},
});
// 3. 建立 Provider 元件
export function ThemeContextProvider({ children }: { children: React.ReactNode }) {
const [mode, setMode] = useState<PaletteMode>('light');
const colorMode = useMemo(
() => ({
toggleColorMode: () => {
setMode((prev) => (prev === 'light' ? 'dark' : 'light'));
},
}),
[]
);
const theme = useMemo(() => createTheme(getDesignTokens(mode)), [mode]);
return (
<ColorModeContext.Provider value={colorMode}>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</ColorModeContext.Provider>
);
}
// 4. 在元件中使用
function ThemeToggleButton() {
const { toggleColorMode } = useContext(ColorModeContext);
return <button onClick={toggleColorMode}>切換主題</button>;
}
跟隨系統設定
如果希望預設跟隨使用者的系統設定,可以使用 window.matchMedia:
const getInitialMode = (): PaletteMode => {
// 檢查是否有儲存的偏好
const savedMode = localStorage.getItem('themeMode') as PaletteMode;
if (savedMode) return savedMode;
// 否則跟隨系統設定
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
};
const [mode, setMode] = useState<PaletteMode>(getInitialMode);
Typography 字型設定
Typography 定義了應用程式中所有文字的視覺規範,包括字型家族、大小、粗細等。
全域字型設定
const theme = createTheme({
typography: {
// 字型家族(會套用到所有文字)
fontFamily: [
'Inter',
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'sans-serif',
].join(','),
// 基礎字體大小(預設 14px)
fontSize: 14,
// HTML font-size(用於 rem 計算,預設 16px)
htmlFontSize: 16,
},
});
Typography Variants
MUI 提供 13 種預設的文字變體,每種都可以獨立客製化:
| 變體 | 預設 HTML 標籤 | 用途 |
|---|---|---|
| h1 ~ h6 | <h1> ~ <h6> | 標題 |
| subtitle1, subtitle2 | <h6> | 副標題 |
| body1, body2 | <p> | 內文 |
| button | <span> | 按鈕文字 |
| caption | <span> | 說明文字 |
| overline | <span> | 標籤文字 |
const theme = createTheme({
typography: {
h1: {
fontSize: '2.5rem',
fontWeight: 700,
lineHeight: 1.2,
},
body1: {
fontSize: '1rem',
lineHeight: 1.6,
},
button: {
textTransform: 'none', // 取消預設的大寫轉換
fontWeight: 600,
},
},
});
MUI 預設會將 Button 的文字轉為大寫。如果不需要這個行為,可以在 typography.button 中設定 textTransform: 'none'。
Spacing 間距系統
MUI 使用一致的間距系統來確保 UI 元素之間的距離保持和諧。預設以 8px 作為基礎單位。