
Refine v5 与 Material UI 主题定制完全指南从 RefineThemes 到深色模式【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine主题决定了应用中组件的配色、表面的明暗、阴影的层次以及墨色元素文字、图标的透明度。在 Refine v5 的 Material UI 集成refinedev/mui中你既可以直接选用官方预置的RefineThemes主题也可以通过ThemeProvider注入自建主题甚至结合createTheme()、responsiveFontSizes()与模块扩充module augmentation实现完全个性化的设计系统。读完本文你将掌握如何接入预定义主题、如何覆写palette与typography、如何从零创建自定义主题、如何实现跟随系统偏好与一键切换的深色模式以及如何让通知 Snackbar 与主题保持同步。主题在 Refine Material UI 中扮演什么角色在 Refine 的 Material UI 集成中主题Theme是一个集中描述视觉样式的对象它统一定义了组件的颜色、表面深浅、阴影级别、墨色元素文字、图标等的透明度等。它的价值在于通过一处定义、全局生效的方式让整个应用的设计风格保持一致避免在数百个组件里零散地硬编码颜色。Refine 官方推荐用create refine-app初始化项目它会根据你的需求完成项目配置包括与 Next.js 一起使用时的 SSR服务端渲染与主题支持让主题在首屏渲染阶段就能正确注入避免样式闪烁。预定义主题 RefineThemes开箱即用的配色方案如果你不想从零设计refinedev/mui包提供了预定义主题集合RefineThemes可以从refinedev/mui直接导入使用// 浅色主题 const { Blue, Purple, Magenta, Red, Orange, Yellow } RefineThemes; // 深色主题 const { BlueDark, PurpleDark, MagentaDark, RedDark, OrangeDark, YellowDark } RefineThemes;在应用入口处用ThemeProvider包裹即可全局生效import { Refine } from refinedev/core; import { ThemedLayout, RefineThemes } from refinedev/mui; import { ThemeProvider } from mui/material/styles; const App: React.FC () { return ( ThemeProvider theme{RefineThemes.Blue} Refine /* ... */ ThemedLayout{/* ... */}/ThemedLayout /Refine /ThemeProvider ); };从源码看 RefineThemes 的构成RefineThemes并非魔法它的底层实现位于 packages/mui/src/theme/index.ts源码遍历 refinePalette.ts 中定义的RefinePalettes对象对每一种配色调用createTheme()生成完整主题并为MuiButton的containedprimary组合设置白色文字以保证对比度。每种配色都包含mode与primary三档色值main/light/dark下面是部分真实定义见 refinePalette.ts主题modeprimary.mainprimary.lightprimary.darkBluelight#1976D2#4791db#115293BlueDarkdark#67b7f7#85c5f8#4880acPurplelight#7B1FA2#954bb4#561571PurpleDarkdark#AB47BC#bb6bc9#773183Magentalight#C2185B#ce467b#87103fRedlight#D32F2F#db5858#932020Orangelight#F57C00#f79633#ab5600Yellowlight#FFA000#ffb333#b27000Greenlight#689F38#86b25f#486f27除文档列出的 6 组明暗主题外refinePalette.ts 中还定义了Green/GreenDark两组配色同样会被自动打包进RefineThemes。值得注意的是refinedev/mui还导出了LightTheme与DarkTheme两个默认主题见 packages/mui/src/theme/index.ts它们以lightPalette/darkPalette为基础并带有一组组件级样式覆写例如MuiAppBar的默认背景、MuiPaper的渐变backgroundImage、MuiTypography的 h5 字重800与行高2rem。所有主题共享shape.borderRadius: 6与同一套字体栈见 typography.ts。关于切换主题后应用实际观感的变化可以参考 Refine 主题示例。ThemeProvider主题注入的核心机制Material UI 的ThemeProvider本质上是 React Context API 的一个简单包装它允许你把一个 Theme 对象注入到整棵组件树中。Material UI 组件默认自带一套主题而通过ThemeProvider你可以注入自己创建的自定义主题获得极高的设计自由度。一个完整的接入示例含路由、数据提供者与ThemedLayoutimport { Refine } from refinedev/core; import { ThemedLayout, ErrorComponent } from refinedev/mui; import { CssBaseline, GlobalStyles, ThemeProvider } from mui/material; import dataProvider from refinedev/simple-rest; import routerProvider from refinedev/react-router; import { BrowserRouter, Routes, Route, Outlet } from react-router; import { PostsList, PostCreate, PostEdit } from pages/posts; const App: React.FC () { return ( ThemeProvider theme{YOUR_THEME_OBJECT} CssBaseline / GlobalStyles styles{{ html: { WebkitFontSmoothing: auto } }} / BrowserRouter Refine routerProvider{routerProvider} dataProvider{dataProvider(https://api.fake-rest.refine.dev)} resources{[ { name: posts, list: /posts, create: /posts/create, edit: /posts/edit/:id, }, ]} Routes Route element{ ThemedLayout Outlet / /ThemedLayout } Route pathposts Route index element{PostList /} / Route pathcreate element{PostCreate /} / Route pathedit/:id element{PostEdit /} / /Route Route path* element{ErrorComponent /} / /Route /Routes /Refine /BrowserRouter /ThemeProvider ); }; export default App;几点实操建议CssBaseline负责抹平浏览器默认样式差异并让theme.palette.background等主题色作用到body上GlobalStyles中的WebkitFontSmoothing: auto用于优化 WebKit 内核下的字体渲染若不包裹ThemeProvider应用将以 Material UI 默认主题渲染包裹后例如传入RefineThemes.Blue整站组件会立即切换为对应配色。覆写主题变量palette 与 typography定制主题的最佳实践是修改配置变量而非逐个改组件。两个最常用的配置区块是palette调色板与typography字体排印。覆写调色板 palette基于RefineThemes.Blue覆写主色与次色只需在createTheme()中展开原主题后重写对应字段import { RefineThemes } from refinedev/mui; import { createTheme } from mui/material/styles; const overriddenLightTheme createTheme({ ...RefineThemes.Blue, palette: { ...RefineThemes.Blue.palette, primary: { main: #44d0c7, }, secondary: { main: #2f82f1, }, }, });注意这里的关键点是...RefineThemes.Blue.palette先展开原有调色板再局部覆盖这样background、text、divider等其余颜色仍然保留避免整体塌陷成一套不完整的配色。覆写完成后将overriddenLightTheme传入ThemeProvider theme{overriddenLightTheme}即可全局生效。覆写字体族 typography同样可以覆写默认字体族。Refine 的 Material UI 集成默认字体栈是Montserrat 优先的系统字体栈见 packages/mui/src/theme/typography.tsimport { RefineThemes } from refinedev/mui; import { TypographyVariantsOptions, createTheme } from mui/material/styles; const typography: TypographyVariantsOptions { fontFamily: [ Montserrat, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, ].join(,), }; const overriddenLightTheme createTheme({ ...RefineThemes.Blue, typography: { ...typographyOptions, }, });若你覆写了fontFamily还需要在index.html中引入对应字体的link标签否则浏览器会回退到下一个可用字体!DOCTYPE html html langen head link hrefhttps://fonts.googleapis.com/css2?familyMontserrat:wght100;200;300;400;500;600;700;800;900displayswap relstylesheet / titleRefine adding font family example/title /head body ... /body /htmlMaterial UI 官方文档提供了更多主题配置变量的说明如spacing、breakpoints、shape等可对照查阅按需覆写。创建自定义主题createTheme 与 responsiveFontSizes除了基于RefineThemes覆写你还可以用createTheme()从零构建主题并在整个应用中使用import { Refine } from refinedev/core; import { ThemedLayout, ErrorComponent } from refinedev/mui; import { CssBaseline, GlobalStyles, ThemeProvider } from mui/material; import { createTheme, responsiveFontSizes } from mui/material; import dataProvider from refinedev/simple-rest; import routerProvider from refinedev/react-router; import { BrowserRouter, Routes, Route, Outlet } from react-router; import { SampleList } from ./pages/samples; let customTheme createTheme({ palette: { primary: { main: #330f49, }, secondary: { main: #45d0c8, }, }, }); customTheme responsiveFontSizes(customTheme); const App: React.FC () { return ( ThemeProvider theme{customTheme} CssBaseline / GlobalStyles styles{{ html: { WebkitFontSmoothing: auto } }} / BrowserRouter Refine routerProvider{routerProvider} dataProvider{dataProvider(https://api.fake-rest.refine.dev)} resources{[ { name: samples, list: /samples, }, ]} Routes Route element{ ThemedLayout Outlet / /ThemedLayout } Route pathsamples Route index element{SampleList /} / /Route Route path* element{ErrorComponent /} / /Route /Routes /Refine /BrowserRouter /ThemeProvider ); };这里额外使用了responsiveFontSizes()辅助函数它会根据视口尺寸自动调整typography各变体的字号例如在窄屏缩小 h1~h6 的字号让排版自适应不同设备。一个更贴近生产的范例是 examples/finefoods-material-ui/src/theme.ts该文件基于RefineThemes.Orange/RefineThemes.OrangeDark构建明暗两套主题覆写了MuiChip、MuiCssBaseline、MuiTypography等组件并对两套主题分别调用responsiveFontSizes()导出响应式版本。为自定义主题添加自定义变量module augmentation如果你需要在主题中携带业务自定义字段例如品牌色 token可以直接在createTheme里写入const customTheme createTheme({ customVariable: { custom: #330f49, }, });但此时 TypeScript 会报错——默认的Theme/ThemeOptions类型并不认识customVariable。你需要借助 TypeScript 的模块扩充机制在theme.d.ts中扩充 MUI 的类型声明import refinedev/mui; export interface CustomTheme { customVariable: { custom: string; }; } declare module mui/material/styles { interface Theme extends import(mui/material/styles).Theme, CustomTheme {} interface ThemeOptions extends import(mui/material/styles).ThemeOptions, CustomTheme {} }这样Theme与ThemeOptions都合并进了customVariable在组件中通过useTheme()读取该字段时也能获得完整的类型提示。真实项目的参照实现见 finefoods-material-ui 的 theme.ts 与 interfaces/index.d.ts。深色模式跟随系统偏好与一键切换深色模式有两种主流实现方式跟随系统偏好和用户手动切换二者可以结合使用。方式一使用 useMediaQuery 跟随系统偏好通过 MUI 的useMediaQuery钩子可以查询用户对明暗模式的系统偏好并据此自动切换主题import { Refine } from refinedev/core; import { ThemedLayout, ErrorComponent, RefineThemes } from refinedev/mui; import { CssBaseline, GlobalStyles, ThemeProvider } from mui/material; import { useMediaQuery } from mui/material; import dataProvider from refinedev/simple-rest; import routerProvider from refinedev/react-router; import { BrowserRouter, Routes, Route, Outlet } from react-router; import { SampleList, SampleCreate, SampleEdit, SampleShow, } from pages/samples; const App: React.FC () { const prefersDarkMode useMediaQuery((prefers-color-scheme: dark)); return ( ThemeProvider theme{prefersDarkMode ? RefineThemes.BlueDark : RefineThemes.Blue} CssBaseline / GlobalStyles styles{{ html: { WebkitFontSmoothing: auto } }} / BrowserRouter Refine routerProvider{routerProvider} dataProvider{dataProvider(https://api.fake-rest.refine.dev)} resources{[ { name: samples, list: /samples, create: /samples/create, edit: /samples/edit/:id, }, ]} Routes Route element{ ThemedLayout Outlet / /ThemedLayout } Route pathsamples Route index element{SampleList /} / Route pathcreate element{SampleCreate /} / Route pathedit/:id element{SampleEdit /} / Route pathshow/:id element{SampleShow /} / /Route Route path* element{ErrorComponent /} / /Route /Routes /Refine /BrowserRouter /ThemeProvider ); }; export default App;这种方案对偏好深色模式的用户非常友好无需任何手动操作站点就会与操作系统保持一致也消除了用户在不同浏览器间来回切换的困惑。方式二ColorModeContext 实现一键切换如果需要让用户手动切换并记住选择推荐用 Context 维护当前模式。仓库中的 customization-theme-material-ui 示例给出了完整实现。首先是ColorModeContext提供者contexts/index.tsximport React, { PropsWithChildren, createContext, useEffect, useState, } from react; import { ThemeProvider } from mui/material; import { RefineThemes } from refinedev/mui; type ColorModeContextType { mode: string; setMode: () void; }; export const ColorModeContext createContextColorModeContextType( {} as ColorModeContextType, ); export const ColorModeContextProvider: React.FCPropsWithChildren ({ children, }) { const colorModeFromLocalStorage localStorage.getItem(colorMode); const isSystemPreferenceDark window?.matchMedia( (prefers-color-scheme: dark), ).matches; const systemPreference isSystemPreferenceDark ? dark : light; const [mode, setMode] useState( colorModeFromLocalStorage || systemPreference, ); useEffect(() { window.localStorage.setItem(colorMode, mode); }, [mode]); const setColorMode () { if (mode light) { setMode(dark); } else { setMode(light); } }; return ( ColorModeContext.Provider value{{ setMode: setColorMode, mode, }} ThemeProvider theme{mode light ? RefineThemes.Blue : RefineThemes.BlueDark} {children} /ThemeProvider /ColorModeContext.Provider ); };这个实现有三个关键细节优先级优先读取localStorage中保存的colorMode没有时才回退到系统偏好实现首次访问跟随系统、之后记住用户选择持久化useEffect在mode变化时写入localStorage刷新页面后模式不丢失注入ThemeProvider内嵌在 Provider 中根据mode在RefineThemes.Blue与RefineThemes.BlueDark之间切换。然后是用于切换的Header组件components/header.tsx它读取 Context 并渲染明暗图标按钮import { useContext } from react; import AppBar from mui/material/AppBar; import Box from mui/material/Box; import IconButton from mui/material/IconButton; import Stack from mui/material/Stack; import DarkModeOutlined from mui/icons-material/DarkModeOutlined; import LightModeOutlined from mui/icons-material/LightModeOutlined; import { ColorModeContext } from ../contexts; export const Header: React.FC () { const { mode, setMode } useContext(ColorModeContext); return ( AppBar colordefault positionsticky Stack width100% directionrow justifyContentend Box marginRight20px IconButton onClick{() { setMode(); }} {mode dark ? LightModeOutlined / : DarkModeOutlined /} /IconButton /Box /Stack /AppBar ); };最后在App.tsx中把它作为Header属性传给ThemedLayout并让RefineSnackbarProvider包裹Refine完整示例import { Refine } from refinedev/core; import { ThemedLayout, ErrorComponent, RefineSnackbarProvider, useNotificationProvider, } from refinedev/mui; import { CssBaseline, AppBar, IconButton, Box, Stack } from mui/material; import dataProvider from refinedev/simple-rest; import routerProvider from refinedev/react-router; import { BrowserRouter, Routes, Route, Outlet } from react-router; import { LightModeOutlined, DarkModeOutlined } from mui/icons-material; import { SampleList, SampleCreate, SampleEdit, SampleShow, } from pages/samples; import { ColorModeContextProvider, ColorModeContext } from ./contexts; const Header () { const { mode, setMode } useContext(ColorModeContext); return ( AppBar colordefault positionsticky Stack width100% directionrow justifyContentend Box marginRight20px IconButton onClick{() { setMode(); }} {mode dark ? LightModeOutlined / : DarkModeOutlined /} /IconButton /Box /Stack /AppBar ); }; const App: React.FC () { return ( ColorModeContextProvider CssBaseline / RefineSnackbarProvider BrowserRouter Refine notificationProvider{useNotificationProvider} routerProvider{routerProvider} dataProvider{dataProvider(https://api.fake-rest.refine.dev)} resources{[ { name: samples, list: /samples, create: /samples/create, edit: /samples/edit/:id, }, ]} Routes Route element{ ThemedLayout Header{Header} Outlet / /ThemedLayout } Route pathsamples Route index element{SampleList /} / Route pathcreate element{SampleCreate /} / Route pathedit/:id element{SampleEdit /} / Route pathshow/:id element{SampleShow /} / /Route Route path* element{ErrorComponent /} / /Route /Routes /Refine /BrowserRouter /RefineSnackbarProvider /ColorModeContextProvider ); }; export default App;整个交互闭环为用户点击 Header 中的图标 →setMode()切换 Context 状态 →ColorModeContextProvider重新选择Blue/BlueDark→ThemeProvider重渲染 → 全站主题即时切换并写入localStorage。你可以对照 customization-theme-material-ui 示例 直接运行体验。通知 Snackbar 与主题同步RefineSnackbarProviderrefinedev/mui的通知系统基于notistack库。之所以选用 Notistack一个重要原因就是当SnackbarProvider作为ThemeProvider的子节点时它能够与主题协同工作通知条会自动继承当前主题的配色。Refine 在此基础上提供了扩展了SnackbarProvider的RefineSnackbarProvider它用styled按主题变量定制了各变体的背景色与文字色ThemeProvider theme{RefineThemes.Blue} CssBaseline / GlobalStyles styles{{ html: { WebkitFontSmoothing: auto } }} / RefineSnackbarProvider BrowserRouter Refine routerProvider{routerProvider} dataProvider{dataProvider(https://api.fake-rest.refine.dev)} notificationProvider{useNotificationProvider} resources{[ { name: samples, list: /samples, create: /samples/create, edit: /samples/edit/:id, }, ]} Routes Route element{ ThemedLayout Outlet / /ThemedLayout } Route pathsamples Route index element{SampleList /} / Route pathcreate element{SampleCreate /} / Route pathedit/:id element{SampleEdit /} / /Route Route path* element{ErrorComponent /} / /Route /Routes /Refine /BrowserRouter /RefineSnackbarProvider /ThemeProvider必须同时做两件事用RefineSnackbarProvider包裹Refine并把notificationProvider{useNotificationProvider}作为 props 传给Refine。从源码看它的实现细节refineSnackbarProvider/index.tsx默认将 Snackbar 定位在top/right并禁用disableWindowBlurListener避免窗口失焦时通知自动消失通过styled覆写.SnackbarItem-contentRoot使用theme.palette.background.default作为背景、theme.palette.primary.main作为文字色variantSuccess/variantError/variantInfo/variantWarning分别使用对应语义色的main与contrastText。因此当你切换RefineThemes.Blue与BlueDark时通知条会自动跟随明暗配色无需额外适配。如果你希望使用 notistack 的原生默认样式也可以直接退而使用SnackbarProvider包裹Refine。小结与进一步探索本文完整覆盖了 Refine v5 Material UI 主题定制的全链路选用预定义主题从refinedev/mui导入RefineThemesBlue / Purple / Magenta / Red / Orange / Yellow 及对应 Dark 变体其底层由 theme/index.ts 基于 refinePalette.ts 自动生成注入主题用ThemeProviderCssBaselineGlobalStyles建立全局主题上下文覆写变量通过展开再局部覆盖的方式修改palette与typography从零建主题createTheme()responsiveFontSizes()并用模块扩充支持自定义变量深色模式useMediaQuery跟随系统偏好或基于ColorModeContext实现一键切换与持久化通知同步RefineSnackbarProvider让 notistack 通知条自动继承主题色。如果你想继续深入推荐阅读仓库中的两个入口一是完整的 customization-theme-material-ui 示例明暗切换 通知二是生产级 finefoods-material-ui 主题文件基于RefineThemes.Orange的深度定制。若需要自定义ThemedLayout的默认布局元素可参考 自定义布局教程项目初始化方式见 快速开始。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考