首页
/ Material UI 样式定制指南:按层级选择 `sx`、`styled()`、主题覆盖与全局 CSS

Material UI 样式定制指南:按层级选择 `sx`、`styled()`、主题覆盖与全局 CSS

2026-09-07 10:03:48作者:裴麒琰

Material UI(本仓库为 mui/material-ui)为开发者提供了从「一次性微调」到「全局基线」的四层样式定制策略。本文以仓库内置的 skills/material-ui-styling/AGENTS.md 为骨架,结合 How to customizeThemed componentsThe sx propstyled() 等官方文档与对应可运行 Demo 源码,系统梳理每一种策略的适用场景、API 细节、状态类与槽位(slot)命名规则。读完你将能够快速判断一个改动该落在哪一层,并写出既符合设计体系、又避免样式「泄漏」到全局的 Material UI 代码。

版本提示:本指南对应的 Material UI 版本为 v9(>=9.0.0 <10.0.0)。若你使用其他主版本,请以实际仓库与所安装包的 API 为准。


1. 概览:四种策略,按作用域从小到大选择

Material UI 的样式定制策略可以按作用域从窄到宽排成一条决策链:

  1. 一次性定制 / 局部布局 → 使用 sx prop
  2. 同一套覆盖在多处复用 → 用 styled() 包裹 MUI 组件(或做一个薄封装组件)
  3. 某个组件所有实例的默认外观都要改变 → 使用 theme.componentsstyleOverridesvariantsdefaultProps
  4. 原生 HTML 元素基线(例如所有 h1)或与单个组件无关的全局样式 → 使用 GlobalStylesCssBaseline 覆盖

两条反向的告诫同样重要:不要为了一个一次性页面就跳到全局主题覆盖;反过来,如果是一个被大规模重复使用的系统样式,也不要只用 sx 到处粘贴——此时具名变体或 styled() 封装更清晰、更易维护。这也是 AGENTS.md 中强调「选择能解决问题的最小作用域,以避免全局规则被四处打散」的原因。


2. 一次性定制:sx prop

2.1 使用场景与能力

当只需要修改单个实例(或很小的内联场景),且能访问到主题时,首选 sx。仓库中的基础示例 SxProp.js 展示了它的典型形态:

import Slider from '@mui/material/Slider';

export default function SxProp() {
  return <Slider defaultValue={30} sx={{ width: 300, color: 'success.main' }} />;
}

color: 'success.main' 这类值并不是合法 CSS,它是 sx 提供的主题感知属性color 会解析为 theme.palette.success.main 路径,width: 300 会变成 300px。MUI System 把所有样式函数打包进了 sx 这个「CSS 超集」里,因此你既能写任意标准 CSS,也能用主题快捷值。官方 The sx prop 中罗列了以下典型映射:

  • 边框border: 1 等价于 border: '1px solid black'borderColor: 'primary.main' 等价于取 theme.palette.primary.mainborderRadius: 2 表示 2 * theme.shape.borderRadius(默认每个单位 4px)。
  • 间距margin / padding 及其长写属性会把数值乘以 theme.spacing(默认每个单位 8px),并支持大量别名——mmtmrmbmlmxmypptprpbplpxpy
  • 调色板colorbgcolorbackgroundColor 别名)接受主题调色板路径字符串。
  • 网格gaprowGapcolumnGap 的数值会乘以 theme.spacing
  • 定位 / 阴影zIndex: 'tooltip' 映射到 theme.zIndex.tooltipboxShadow: 1 映射到 theme.shadows[1]
  • 字号排版fontWeight: 'fontWeightLight'(或省略前缀的 'light')映射到 theme.typographytypography: 'body1' 会展开 theme.typography.body1 的全部值。
  • 尺寸width: 1/2 会转换为 '50%'(值在 (0, 1] 之间时按百分比处理),width: 20 则输出 '20px'

此外 sx 还支持伪选择器、嵌套选择器、响应式对象(断点与容器查询)、回调与数组值

2.2 数组语法:条件合并的正确姿势

当需要根据条件叠加样式时,用数组形式代替对象展开(spread):

<Box
  sx={[
    {
      '&:hover': {
        color: 'red',
        backgroundColor: 'white',
      },
    },
    foo && {
      '&:hover': { backgroundColor: 'grey' },
    },
    bar && {
      '&:hover': { backgroundColor: 'yellow' },
    },
  ]}
/>

规则是:数组按顺序应用,falsy 条目会被跳过;同一属性上索引越大优先级越高——因此上例中即使 foo 为真,只要 bar 为真,backgroundColor 就是 yellow。每个索引既可以是一个样式对象,也可以是一个接收 theme 的回调:

sx={[
  { mr: 2, color: 'red' },
  (theme) => ({
    '&:hover': {
      color: theme.palette.primary.main,
    },
  }),
]}

注意:回调应作为整个 sx 值使用sx={(theme) => ({...})}),属性值位置的回调形式已废弃。TypeScript 下把样式对象单独声明时需要 as const,以避免字面量类型被拓宽为 string 导致类型不匹配。

2.3 覆盖组件内部槽位(slots)

要修改组件的某个内部子结构,可以在 sx 里用全局类名片段定位槽位,例如把 Slider 的滑块 thumb 改成方形:

import Slider from '@mui/material/Slider';

export default function DevTools() {
  return (
    <Slider
      defaultValue={30}
      sx={{
        width: 300,
        color: 'success.main',
        '& .MuiSlider-thumb': {
          borderRadius: '1px',
        },
      }}
    />
  );
}

完整 Demo 见 DevTools.js。这里的思路是:先在浏览器 DevTools 里找到目标槽位的类名。Material UI 注入的类名遵循固定模式 [hash]-Mui[组件名]-[槽位名],例如 .css-ae2u5c-MuiSlider-thumb——但 hash 部分不稳定,绝不能拿来写选择器,只能依赖稳定的 Mui[Component]-[slot] 片段(如 .MuiSlider-thumb)。该命名规范与更完整的槽位说明参见 reference.md

2.4 状态类(state classes)与特异性

hoverfocusdisabledselected 这类状态在 Material UI 里用高特异性样式表达。能使用原生伪类的状态(如 :disabled)优先使用伪类;但像 selected 这类 Web 规范中不存在对应伪类的状态,Material UI 提供了状态类(全局类名),其特异性与 CSS 伪类相当。覆盖它们时必须提高特异性,永远不要把状态类当裸全局选择器使用:

/* ❌ 错误:会污染所有使用 .Mui-error 的组件 */
.Mui-error {
  color: red;
}

/* ✅ 正确:限定到 OutlinedInput 的根元素上 */
.MuiOutlinedInput-root.Mui-error {
  color: red;
}

对完整状态类清单与规则,见 How to customize 的 "State classes" 小节及下方的状态类速查表。

2.5 className:接入外部 CSS / CSS Modules

当需要与外部样式库或 CSS Modules 协作时,使用组件自带的 className prop,并按同样的「槽位 + 状态类」规则组合选择器(例如 className="Button" 配合 .Button:disabled 提权)。各样式库的完整接入方式见仓库的 interoperability 相关示例。


3. 可复用:styled()

3.1 何时使用

同一个自定义组件要出现在多个位置、且值得拥有一个具名组件时,把 styled() 作为首选:

import { styled } from '@mui/material/styles';

使用 Material UI 时应优先从 @mui/material/styles 导入,这样在应用没有通过 ThemeProvider 提供主题时,会用与其余应用一致的默认主题兜底;而 @mui/system 版本的 styled 使用另一套默认主题。仓库中的可运行对照例子是 StyledCustomization.js

import Slider from '@mui/material/Slider';
import { alpha, styled } from '@mui/material/styles';

const SuccessSlider = styled(Slider)(({ theme }) => ({
  width: 300,
  color: theme.palette.success.main,
  '& .MuiSlider-thumb': {
    '&:hover, &.Mui-focusVisible': {
      boxShadow: `0px 0px 0px 8px ${alpha(theme.palette.success.main, 0.16)}`,
    },
    '&.Mui-active': {
      boxShadow: `0px 0px 0px 14px ${alpha(theme.palette.success.main, 0.16)}`,
    },
  },
}));

export default function StyledCustomization() {
  return <SuccessSlider defaultValue={30} />;
}

相比底层样式库(Emotion / styled-components)的 styled(),Material UI 版本额外提供了四个能力(见 styled() 文档):

  1. 无主题上下文时使用默认主题
  2. 支持通过 options.name 关联 theme.components[name] 下的 styleOverridesvariants
  3. 生成的组件自带 sx prop(可用 options.skipSx 关闭);
  4. 默认内置 shouldForwardProp 处理,避免把 ownerStatethemesxas 等透传到 DOM。

3.2 options 参数速览

styled(Component, options)(styles) 中可用的关键 options:

  • shouldForwardProp(prop: string) => bool,决定某个 prop 是否继续传递给底层组件;
  • nametheme.components 下用于查找 styleOverrides / variants 的键,同时也用于生成调试用 label;
  • slot:通常为 'Root',为 Root 时自动应用主题里该组件的 variants
  • overridesResolver:自定义如何根据 props 解析 theme.components[name].styleOverrides
  • skipVariantsResolver:设为 true 可关闭对 theme.components[name].variants 的自动解析;
  • skipSx:设为 true 可关闭结果组件上的 sx 支持。

3.3 自定义 props 与动态样式

为自定义组件新增 props 时,务必使用 shouldForwardProp 把非 DOM 属性拦下来,避免 React 把无效属性渲染到 DOM 上;TypeScript 侧则通过继承组件 props 类型并做接口扩展。仓库 DynamicCSS.js 演示了标准写法:

const StyledSlider = styled(Slider, {
  shouldForwardProp: (prop) => prop !== 'success',
})(({ theme }) => ({
  width: 300,
  variants: [
    {
      props: ({ success }) => success,
      style: {
        color: theme.palette.success.main,
        '& .MuiSlider-thumb': {
          '&:hover, &.Mui-focusVisible': {
            boxShadow: `0px 0px 0px 8px ${alpha(theme.palette.success.main, 0.16)}`,
          },
          '&.Mui-active': {
            boxShadow: `0px 0px 0px 14px ${alpha(theme.palette.success.main, 0.16)}`,
          },
        },
      },
    },
  ],
}));

动态样式的两条实现路径建议:

  • 首选 CSS 变量或样式回调中的条件样式对象:把「按 props 分支」收敛在一个顶层函数里,可读性最好;不要在样式对象内部给每个字段单独写 (props) => ... 形式的函数。
  • 需要随高频变化值(如颜色选择器实时预览)改样式时,优先用内联 CSS 变量而不是每帧传入新对象,避免不断向 DOM 插入无谓的 <style> 标签而引发性能问题(参见 The sx prop—Dynamic values)。

4. 全站一致:createTheme({ components })

4.1 何时使用

当希望 ButtonTextField 等组件默认外观在全应用范围内统一变化时,使用主题的 components 键。它的三个子键分工明确:defaultProps 改默认 props、styleOverrides 改槽位样式、variants 按 props 映射附加样式。完整的组件键与槽位名需要查阅对应组件的 "Customization" 文档小节,键名与组件内部名一致(如 MuiButtonMuiTextField)。

关键告诫:主题是不可 tree-shaking 的(见 theme-components.md)。如果某个定制很重却只用在一两处,新建一个组件通常比把主题越撑越大更好。

4.2 defaultProps:修改默认 props

const theme = createTheme({
  components: {
    // 组件名
    MuiButtonBase: {
      defaultProps: {
        // 要修改默认值的 props
        disableRipple: true, // 全应用关闭涟漪效果
      },
    },
  },
});

4.3 styleOverrides:按槽位覆盖样式

styleOverrides槽位名为键(root 表示最外层元素),值为 CSS-in-JS 样式对象,也支持嵌套选择器:

const theme = createTheme({
  components: {
    MuiButton: {
      styleOverrides: {
        // 槽位名
        root: {
          // CSS 属性
          fontSize: '1rem',
        },
      },
    },
  },
});

当需要依据组件已解析的 props(ownerState) 或主题值分支时,可把某槽位值写成回调形式(例如 root: ({ ownerState, theme }) => ({ ... }))。需要注意:仓库内 Themed components 文档指出,以回调访问槽位 ownerState 的形式已被标记为废弃(deprecated),官方建议改用 variants 表达按 props 分支的样式:

 const theme = createTheme({
   components: {
     MuiButton: {
       styleOverrides: {
-        root: ({ ownerState, theme }) => ({ ... }),
+        root: {
+          variants: [...],
         },
       },
     },
   },
 });

4.4 variants:把 props 映射成样式

variants 定义在具体槽位下,是 { props, style } 对象构成的数组。当组件 props 与 props 匹配时应用 style需要优先的样式要放在数组后面

针对既有 props 增加样式(例如加粗 outlined 类型的 Card 边框):

const theme = createTheme({
  components: {
    MuiCard: {
      styleOverrides: {
        root: {
          variants: [
            {
              props: { variant: 'outlined' },
              style: {
                borderWidth: '3px',
              },
            },
          ],
        },
      },
    },
  },
});

为 Button 增加全新变体 dashed(取值名可自定义):

const theme = createTheme({
  components: {
    MuiButton: {
      styleOverrides: {
        root: {
          variants: [
            {
              props: { variant: 'dashed' },
              style: {
                textTransform: 'none',
                border: `2px dashed ${blue[500]}`,
              },
            },
          ],
        },
      },
    },
  },
});

也可以同时匹配既有与新增 props,甚至把 props 写成回调来处理「属性不等于某值」这类条件(例如 variant === 'dashed' && color !== 'secondary')。若在 TypeScript 中使用新增变体,需要通过[模块增强]声明 ButtonPropsVariantOverrides

declare module '@mui/material/Button' {
  interface ButtonPropsVariantOverrides {
    dashed: true;
  }
}

仓库还用类型测试锁定了这一用法(见 packages/mui-material/test/typescript/augmentation/ 下的组件主题相关 spec)。

4.5 在主题里使用 sx 语法(实验性)

sx 在组件上自 v5 起就是稳定特性,但在主题对象内部直接使用仍属实验性。若你已经在用 sx,可以用同样的语法快速迁移样式到主题:

const finalTheme = createTheme({
  components: {
    MuiChip: {
      styleOverrides: {
        root: ({ theme }) =>
          theme.unstable_sx({
            px: 1,
            py: 0.25,
            borderRadius: 1,
          }),
        label: {
          padding: 'initial',
        },
        icon: ({ theme }) =>
          theme.unstable_sx({
            mr: 0.5,
            ml: '-2px',
          }),
      },
    },
  },
});

由于 sx 的 CSS 特异性高于主题覆盖,即便在主题中用了这套语法,仍可继续在组件上通过 sx 覆盖它。


5. 全局 CSS:GlobalStylesCssBaseline

5.1 何时使用

当要样式化原生 HTML 元素(例如所有 h1)或编写不绑定到某个 MUI 组件实例的全应用级代码片段时使用。

5.2 GlobalStyles 基本用法

import * as React from 'react';
import GlobalStyles from '@mui/material/GlobalStyles';

export default function GlobalCssOverride() {
  return (
    <React.Fragment>
      <GlobalStyles styles={{ h1: { color: 'grey' } }} />
      <h1>Grey h1 element</h1>
    </React.Fragment>
  );
}

完整示例见 GlobalCssOverride.js。当需要访问主题时,把 styles 写成回调即可:

<GlobalStyles
  styles={(theme) => ({
    h1: { color: theme.palette.primary.main },
  })}
/>

(见 GlobalCssOverrideTheme.js。)

性能建议:把 <GlobalStyles /> 提升为模块级常量再复用,避免每次渲染都重新计算 <style> 标签、造成重复的样式计算:

 import * as React from 'react';
 import GlobalStyles from '@mui/material/GlobalStyles';

+const inputGlobalStyles = <GlobalStyles styles={...} />;

 function Input(props) {
   return (
     <React.Fragment>
-      <GlobalStyles styles={...} />
+      {inputGlobalStyles}
       <input {...props} />
     </React.Fragment>
   );
 }

5.3 通过 MuiCssBaseline 扩展全局基线

CssBaseline 用于为应用提供统一的 HTML 基线(去除 body 默认 margin、应用 theme.palette.background.default 背景、全局 border-box、滚动条与 color-scheme 等,详见 CSS Baseline)。如果你已经在用 CssBaseline,与其再叠加一个 GlobalStyles,不如直接扩展 MuiCssBaselinestyleOverrides

import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider, createTheme } from '@mui/material/styles';

const theme = createTheme({
  components: {
    MuiCssBaseline: {
      styleOverrides: `
        h1 {
          color: grey;
        }
      `,
    },
  },
});

export default function OverrideCssBaseline() {
  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <h1>Grey h1 element</h1>
    </ThemeProvider>
  );
}

MuiCssBaselinestyleOverrides 同样支持回调形式以访问主题(参考 OverrideCssBaseline.js 及配套的 callback 示例)。若只是渐进式迁移某块局部区域而不想全局重置,可以考虑 ScopedCssBaseline,把基线限定在其子节点内。


6. sx vs styled():Agent 与开发者都应知道的差异

两者经常被混淆,Material UI 官方文档与 AGENTS.md 都专门列出一张差异对照表:

主题 sx styled() 样式对象
主题间距简写(mpgap 等) ✅ 支持 ❌ 不支持。需在回调中用 theme.spacing() 或写普通 CSS 值
数字 padding(如 1)的含义 主题间距单位(theme.spacing(1) 像素(1px),不是 theme.spacing(1)
主题调色板字符串(如 'primary.main' ✅ 支持 需在回调中使用 theme 取值

由此可总结出三条最容易踩的「坑」:

  • styled('button')({ mx: 1 }) 是错误用法——mx 这类系统布局简写只在 sx 中可用,styled() 的样式对象是纯 CSS,必须写 margin-left: theme.spacing(1)
  • styled('button')({ padding: 1 }) 中的 1 代表 1px;而 sx={{ padding: 1 }} 中的 1 代表 theme.spacing(1)。语义完全不同。
  • 想在 styled() 里复用一套 sx 逻辑时,可借助主题的 unstable_sx(写法参考上文 4.5 节),细节见 styled()—Difference with the sx prop

7. 导入与一致性约定

为了让代码整洁且利于打包,仓库内的 AGENTS.md 给出了两条应用层约定:

  • 优先使用包的一级导入,例如 @mui/material/Button,避免通过桶文件(barrel)把整个 @mui/material 拉进产物;
  • 系统布局简写(pgapmt 等)用 sx 表达行为相关的、由组件文档定义的 props 用组件 props 表达——不要把布局语义塞进 variant 之类的行为性 props 里。

示例 Demo 基本都遵循这一约定,例如 SxProp.js@mui/material/Slider 单路径导入组件,而样式靠 sx 完成。


8. 附录:状态类与类名速查

以下状态类与命名规范在 AGENTS.md 的配套文件 reference.md 以及 How to customize 中均有完整记载,使用时务必与组件或限定作用域的选择器组合,绝不单独出现

8.1 全局状态类

状态 全局类名
active .Mui-active
checked .Mui-checked
completed .Mui-completed
disabled .Mui-disabled
error .Mui-error
expanded .Mui-expanded
focus visible .Mui-focusVisible
focused .Mui-focused
readOnly .Mui-readOnly
required .Mui-required
selected .Mui-selected

8.2 槽位类名模式

  • 注入的类名遵循 [hash]-Mui[组件名]-[槽位名]
  • 写选择器时只用稳定片段 Mui[组件名]-[槽位名](如 .MuiSlider-thumb),禁止使用含 hash 的完整类名,因为 hash 不稳定。

8.3 主题组件键

  • 通过 createTheme({ components: { MuiButton: { ... } } }) 覆盖组件。
  • 键名与组件内部名一致(例如 MuiButtonMuiTextField);具体槽位名和可用的主题键请查阅对应组件文档的 Customization 小节。

9. 进一步阅读

主题 仓库内文档
策略总览与决策 How to customize
sx 参考 The sx prop
styled() API styled()
组件级主题 Themed components
CSS 基线 CSS Baseline
状态 / 类名速查 reference.md

在动手前可以再核对一遍 AGENTS.md 中的决策清单:先问「是单实例还是局部布局?」→ 用 sx;再问「同一覆盖是否多处复用?」→ 用 styled();接着问「是否所有实例默认外观都要变?」→ 用 theme.components;最后才考虑「是否是原生元素基线或非组件全局 CSS?」→ 用 GlobalStyles / CssBaseline。按此顺序,从最小作用域开始,你就能始终把定制放在最合适、最不易产生副作用的那一层。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388