MUI System 上手指南:用 `sx` prop 快速构建自定义设计的 CSS 工具集
MUI System 是 Material UI 生态中一套独立的 CSS 工具库,它以 sx prop 为核心,让你能直接在组件内部书写“主题感知”的行内样式,快速完成一次性、自定义化的界面布局与视觉定制。本指南基于仓库中的 System 概览文档,结合其姊妹篇 Usage 文档、the sx prop 文档、Installation 文档,并深入 packages/mui-system 源码,讲清 MUI System 的定位、核心机制、源码实现与最佳使用场景。读完你将掌握:用 sx 在一行内完成主题化样式、按断点响应式取值、把样式能力注入自定义组件,以及在何种取舍下决定是否采用它。
MUI System 是什么
MUI System 是一组 CSS 工具函数(CSS utilities),目标是帮助开发者“更高效地构建自定义设计”,快速完成自定义布局。它不是独立的组件框架,而是被 Material UI 等库内部使用的基础设施——在本仓库 monorepo 中,其源码位于 packages/mui-system/src,Material UI 所在的 packages/mui-material 正是建立在这一层能力之上的产品库。
MUI System 对外提供了若干灵活、通用的包装型组件,例如 Box 与 Container,它们可以通过 sx prop 被快速定制。sx 让你把样式直接写在组件自身内部,而不是像 styled-components 那样为每个小改动单独创建繁冗的 const 定义。同时,sx 允许你直接读取主题中自定义的设计令牌(design tokens),从而保证“一次性样式”也能与全局设计体系保持一致性。
从 System 入口文件 可以看到它的能力全貌:不仅导出 Box、Container、Grid、Stack 等布局组件,还导出 ThemeProvider、styled、createTheme、createBreakpoints、useTheme、useMediaQuery 等一套完整的主题与样式基础设施,以及 borders、display、flexbox、palette、spacing、typography 等风格函数。整套风格函数最终被 sx prop 统一封装。
MUI System 的四大优势
官方 Overview 文档将它的价值浓缩为四点,这四点也正是它与 styled-components 这类方案对比时的核心差异:
1. 写更少的代码
在 styled-components 显得“杀鸡用牛刀”的场景里,sx prop 可以把几十行样式定义压缩到组件内部寥寥几行。对于一次性、不打算复用的定制外观,这能显著降低代码噪音。
2. 写你已经会的 CSS
sx prop 是 CSS 的超集:标准 CSS 属性、选择器、伪类、媒体查询全部可用,额外还叠加了若干 MUI 特有的“主题感知”属性。只要熟悉 CSS,几乎零学习成本即可上手。
3. 避免上下文切换
用 styled-components 时,你需要在“使用处”和“定义处”之间来回跳转才能理解一个组件的全貌。而 MUI System 把样式和使用放在同一个 JSX 位置,心智负担更低。
4. 忘掉无谓的命名
给一个样式组件起名往往令人头疼(StatWrapper、StatHeader、StyledTrend……)。使用 sx 时,这个命名步骤被直接省略。
从 styled-components 到 sx 的直观对比
Usage 文档 用同一个“数据统计卡片”给出了两种写法的完整对照,其演示代码可在此仓库直接查看:Why.js。
方案一:使用 styled-components API
const StatWrapper = styled('div')(
({ theme }) => `
background-color: ${theme.palette.background.paper};
box-shadow: ${theme.shadows[1]};
border-radius: ${theme.shape.borderRadius}px;
padding: ${theme.spacing(2)};
min-width: 300px;
`,
);
const StatHeader = styled('div')(
({ theme }) => `
color: ${theme.palette.text.secondary};
`,
);
const StyledTrend = styled(TrendingUpIcon)(
({ theme }) => `
color: ${theme.palette.success.dark};
font-size: 16px;
vertical-align: sub;
`,
);
const StatValue = styled('div')(
({ theme }) => `
color: ${theme.palette.text.primary};
font-size: 34px;
font-weight: ${theme.typography.fontWeightMedium};
`,
);
const StatDiff = styled('div')(
({ theme }) => `
color: ${theme.palette.success.dark};
display: inline;
font-weight: ${theme.typography.fontWeightMedium};
margin-left: ${theme.spacing(0.5)};
margin-right: ${theme.spacing(0.5)};
`,
);
const StatPrevious = styled('div')(
({ theme }) => `
color: ${theme.palette.text.secondary};
display: inline;
font-size: 12px;
`,
);
return (
<StatWrapper>
<StatHeader>Sessions</StatHeader>
<StatValue>98.3 K</StatValue>
<StyledTrend />
<StatDiff>18.77%</StatDiff>
<StatPrevious>vs last week</StatPrevious>
</StatWrapper>
);
方案二:使用 MUI System 的 sx
<Box
sx={{
bgcolor: 'background.paper',
boxShadow: 1,
borderRadius: 1,
p: 2,
minWidth: 300,
}}
>
<Box sx={{ color: 'text.secondary' }}>Sessions</Box>
<Box sx={{ color: 'text.primary', fontSize: 34, fontWeight: 'medium' }}>
98.3 K
</Box>
<Box
component={TrendingUpIcon}
sx={{ color: 'success.dark', fontSize: 16, verticalAlign: 'sub' }}
/>
<Box
sx={{ color: 'success.dark', display: 'inline', fontWeight: 'medium', mx: 0.5 }}
>
18.77%
</Box>
<Box sx={{ color: 'text.secondary', display: 'inline', fontSize: 12 }}>
vs. last week
</Box>
</Box>
注意几个信息量很大的细节:
bgcolor: 'background.paper'与color: 'text.secondary'是主题调色板路径,而非普通 CSS 值;boxShadow: 1等价于theme.shadows[1];borderRadius: 1会乘以theme.shape.borderRadius;p: 2、mx: 0.5会自动换算为theme.spacing(...);component={TrendingUpIcon}让Box直接渲染为图标组件,这是Box作为可覆盖组件(overridable component)的能力。
同样是“复用主题令牌”,前者每处都要写 ({ theme }) => ... 与模板字符串,后者则是纯声明式对象。
sx 可以在哪些位置使用
Usage 文档 指出,sx prop 可在四个位置使用:
- 核心组件(Core components):Material UI 的所有组件(Button、Card、Typography……)都原生支持
sxprop; Box包装器:Box本身是轻量组件,默认渲染为<div>,是sx最直接的使用载体,同时可以充当其他组件的包装层;- 自定义组件(Custom components):通过
@mui/material/styles(或 MUI System 自己的 styled)把sx能力接入自定义组件:
import { styled } from '@mui/material/styles';
const Div = styled('div')``;
更完整的做法(包括把 sx 从外层组件安全地传递给内部 System 组件、以及通过 StyleFunctionSx 类型组合风格函数)可参阅 custom-components.md;
4. 任意元素(配合 Babel 插件):在官方对 sx Babel 插件的探讨落地之前,此方案仍处于社区讨论阶段,实战中通常优先使用前三种位置。
组件 props 与 sx 的分工
在 API 设计上,MUI 组件把“影响组件行为”的 props(如 Button 的 color 会同时影响 hover、focus 等多重状态)与“纯 CSS 效果”的 sx 分离开。系统属性统一经由 sx 注入,组件 props 则聚焦于有文档定义的行为,从而避免与原生及自定义 props 产生冲突——这是 Usage 文档 特别强调的 API 取舍。
sx 的语义:CSS 超集 + 主题令牌映射
the sx prop 文档 定义:sx 是“访问主题的快捷样式写法”,它把 @mui/system 暴露的全部风格函数打包进一个 prop。sx 的 key 未必是合法 CSS 属性,因为其中很多会被映射到主题对象的具体字段。映射逻辑的“字典”就是源码中的 defaultSxConfig.ts,其核心配置项结构为:
export interface SxConfigRecord {
cssProperty?: keyof React.CSSProperties | false | undefined; // 输出到哪个 CSS 属性
themeKey?: string | undefined; // 在 theme 中的点路径,如 'palette'
transform?: TransformFunction | undefined; // 自定义换算函数
style?: SimpleStyleFunction<any> | undefined; // 复用的风格函数
}
典型的主题映射速查
sx 写法 |
实际效果 | 配置出处(defaultSxConfig.ts) |
|---|---|---|
border: 1 |
border: '1px solid black'(数字仅表示像素宽度,颜色固定为黑色) |
border 项配 borderTransform |
borderColor: 'primary.main' |
border-color: theme.palette.primary.main |
themeKey: 'palette' |
borderRadius: 2 |
border-radius: 2 * theme.shape.borderRadius |
themeKey: 'shape.borderRadius',style: borderRadius |
color: 'primary.main' |
color: theme.palette.primary.main |
themeKey: 'palette' |
bgcolor: 'primary.main' |
backgroundColor,同 palette 路径 |
单独配置 cssProperty: 'backgroundColor' |
m: 2 / p: 2 |
margin/padding: theme.spacing(2) |
复用的 margin / padding 风格函数 |
zIndex: 'tooltip' |
z-index: theme.zIndex.tooltip |
themeKey: 'zIndex' |
boxShadow: 1 |
box-shadow: theme.shadows[1] |
themeKey: 'shadows' |
fontWeight: 'light' |
font-weight: theme.typography.fontWeightLight |
themeKey: 'typography' |
typography: 'body1' |
展开为 { ...theme.typography.body1 } |
cssProperty: false,themeKey: 'typography' |
displayPrint: 'none' |
@media print { display: none } |
cssProperty: false + transform |
spacing 方向别名
spacing 组是最常用的速记,官方为其提供了一整套方向别名(详见 Spacing 页 同目录风格文档与 the-sx-prop.md):
| Prop | CSS property |
|---|---|
m |
margin |
mt / mr / mb / ml |
margin-top / margin-right / margin-bottom / margin-left |
mx |
margin-left 与 margin-right |
my |
margin-top 与 margin-bottom |
p |
padding |
pt / pr / pb / pl |
padding-top / padding-right / padding-bottom / padding-left |
px |
padding-left 与 padding-right |
py |
padding-top 与 padding-bottom |
在源码层面,defaultSxConfig.ts 中 m、mt、mx、p、px 等别名均指向复用的 margin/padding 风格函数;除此之外还支持 paddingX、paddingInline、marginBlock 等更细粒度的逻辑属性别名。
sizing 的百分比换算
宽度/高度类属性采用了一个特殊换算:当值在 (0, 1] 区间内时转换为百分比,否则按原样输出(如 width: 20 会被当作像素)。换算函数如下:
function transform(value) {
return value <= 1 && value !== 0 ? `${value * 100}%` : value;
}
因此 sx={{ width: 1 / 2 }} 等价于 width: '50%',而 sx={{ width: 20 }} 等价于 width: '20px'。
sx 支持完整 CSS:伪类、媒体查询与嵌套选择器
[usage.md](https://gitcode.com/GitHub_Trending/ma/material-ui/blob/95f68f3eb2fc42dcccf1ba2a0c4c956b0f22e452/docs/data/system/getting-started/usage/usage.md?utm_source=gitcode_repo_files) 给出了三种典型写法:
// 伪选择器
<Box
sx={{
':hover': {
boxShadow: 6,
},
}}
/>
// 媒体查询
<Box
sx={{
'@media print': {
width: 300,
},
}}
/>
// 嵌套子选择器
<Box
sx={{
'& .ChildSelector': {
bgcolor: 'primary.main',
},
}}
/>
除了这些“对象嵌套”,sx 还接受函数值(访问任意主题字段)和数组(按索引合并、后者优先级更高,便于条件覆盖样式)。当你想整体取用 theme 中某个对象时,the-sx-prop.md 推荐把回调作为整个 sx 值传入:
<Box
sx={(theme) => ({
...theme.typography.body,
color: theme.palette.primary.main,
})}
/>
响应式取值:对象、数组与自定义断点
sx 大幅简化了响应式断点的书写。仓库演示位于 BreakpointsAsObject、BreakpointsAsArray 等文件。
对象形式(推荐)
以断点名为 key,且某个断点下的属性对更大的断点同样生效:
<Box
sx={{
width: {
xs: 100, // theme.breakpoints.up('xs')
md: 200, // theme.breakpoints.up('md')
},
}}
>
width: { md: 200 } 语义上等价于 theme.breakpoints.up('md')。
数组形式
从最小到最大断点依次排列,可用 null 跳过中间断点:
<Box sx={{ width: [null, null, 300] }}>This box has a responsive width.</Box>
文档建议:仅在主题断点很少(例如 3 个)时考虑数组形式,断点较多时优先用对象 API。
容器查询(v6+)
从 v6 起,断点对象支持以 @ 开头的容器查询简写,语法为 @{breakpoint}/{container}:
breakpoint:px数值、断点 key(默认主题为sm/md/lg/xl)或合法 CSS 值(如40em);container(可选):具名 containment context。
容器查询有浏览器兼容性门槛,使用前应先确认目标浏览器支持情况。
自定义断点
usage.md 提供了一个完整示例,可自由定义业务断点名(如 mobile/tablet/laptop/desktop):
import * as React from 'react';
import Box from '@mui/material/Box';
import { createTheme, ThemeProvider } from '@mui/material/styles';
const theme = createTheme({
breakpoints: {
values: {
mobile: 0,
tablet: 640,
laptop: 1024,
desktop: 1280,
},
},
});
export default function CustomBreakpoints() {
return (
<ThemeProvider theme={theme}>
<Box
sx={{
width: {
mobile: 100,
laptop: 300,
},
}}
>
This box has a responsive width
</Box>
</ThemeProvider>
);
}
TypeScript 项目需要配合模块扩充(module augmentation),把新断点注入主题类型,同时可以关闭默认断点:
declare module '@mui/material/styles' {
interface BreakpointOverrides {
xs: false; // 移除 xs 断点
sm: false;
md: false;
lg: false;
xl: false;
tablet: true; // 新增 tablet 断点
laptop: true;
desktop: true;
}
}
源码视角:sx 究竟是如何被解析的
sx 并不是魔法。真正的解析核心位于 styleFunctionSx.js,其中最关键的执行流程可以概括为:
- 前置短路:若
props.sx不存在则直接返回null; - 选定配置字典:
theme.unstable_sxConfig ?? defaultSxConfig——也就是说,主题可以注入自定义的unstable_sxConfig覆盖默认的 defaultSxConfig.ts,从而扩展自定义属性映射; - 统一求值:若
sx是函数则用theme调用它得到对象;若是数组则逐项递归处理; - 逐 key 分发:对每个 styleKey,如果存在对应配置(如
themeKey、transform、style),则走setThemeValue完成“主题令牌 → CSS”的换算,并自动套用 spacing 单位换算、调色板路径解析、数值百分比化等 transform;如果 value 恰好命中已定义断点的 key 结构,则走iterateBreakpoints展开为媒体查询; - 嵌套递归:对子选择器/伪类等对象值,通过带
nested: true的 wrapper 递归调用styleFunctionSx自身,实现任意深度的嵌套; - 收尾清理:用
removeUnusedBreakpoints去除未使用的断点容器,用sortContainerQueries保证媒体查询与容器查询输出顺序正确;若主题启用了modularCssLayers,还会包进@layer sx。
换言之,sx 的本质是把“key 字典 + 主题令牌 + 断点展开”这一整套风格函数管线串起来的一个统一入口。这也是文档强调“sx 是 CSS 超集、能自动清理未用断点”的实现基础。
Box 与 Container 的组件实现
Box
Box.tsx 的实现非常精简:它通过 createBox({ defaultClassName, generateClassName }) 生成,BoxTypeMap 中 defaultComponent 为 'div',因此默认渲染 <div>,同时它被类型化为 overridable component,可通过 component 属性渲染成任意元素或组件。BoxOwnProps 只声明了 children、ref 与 sx 三个自有 props,其余能力全部来自样式系统——可见它是“只负责承载 sx”的通用载体。
Box 组件自身有一套 Box.test.js 测试保障其行为,包括系统属性、responsive 值、component 覆盖等场景。
Container
Container.tsx 由 createContainer() 生成,常用的受控 props 包括:
maxWidth:决定容器的最大宽度档位,默认'lg',随屏幕尺寸增长;可传false禁用;fixed:让max-width等于当前断点的min-width,默认false(默认是流式宽度,不会跳档);disableGutters:移除左右内边距,默认false。
连同 Grid、Stack 一起,这些通用布局组件与 Box 构成了 System 入口 导出的“布局组件层”。
何时该用 MUI System:取舍与性能
[usage.md](https://gitcode.com/GitHub_Trending/ma/material-ui/blob/95f68f3eb2fc42dcccf1ba2a0c4c956b0f22e452/docs/data/system/getting-started/usage/usage.md?utm_source=gitcode_repo_files) 对适用场景给出了清晰边界:
sx最适用于“一次性样式”:为某个自定义组件快速打补丁、做微调;- styled-components API 更适合“需要支撑多种上下文”的组件:这类组件会在应用许多位置复用,接受不同的 props 组合、需要长期演化。
性能权衡
MUI System 依赖 CSS-in-JS,同时兼容 Emotion 与 styled-components 两种引擎。Usage 文档列举了它的利弊:
优点
- 语法是 CSS 超集,附带(可选的)速记写法;
- 系统会自动清理,只把页面实际用到的 CSS 发给客户端;初始包体成本固定,不会随你添加更多 CSS 属性而膨胀;
- 核心成本来自
@emotion/react与@mui/system,官方文档给出的总体量约 15 kB gzipped(该数值随版本演进会有变化,以你所安装版本实际体积为准);如果已经使用 Material UI 等核心库,则没有额外开销。
缺点
- 运行时渲染性能有损耗。Usage 文档给出的归一化基准(该数据出自仓库内文档示例,可复现验证)如下:
| Benchmark case | Code snippet | Time normalized |
|---|---|---|
| 渲染 1,000 个原始元素 | <div className="…"> |
100ms |
| 渲染 1,000 个普通组件 | <Div> |
112ms |
| 渲染 1,000 个 styled 组件 | <StyledDiv> |
181ms |
| 渲染 1,000 个 Box | <Box sx={…}> |
296ms |
文档同时给出结论:对绝大多数场景足够快;当性能成为瓶颈时存在简单变通——例如渲染长列表时,用外层一个 Box 提供样式注入点,内部列表项使用纯 CSS 子选择器命中,把“每个列表项都跑一遍 sx”的代价摊薄掉。
安装与环境前提
根据 installation.md,MUI System 默认使用 Emotion 作为样式引擎,三条命令任选其一:
npm install @mui/system @emotion/react @emotion/styled
pnpm add @mui/system @emotion/react @emotion/styled
yarn add @mui/system @emotion/react @emotion/styled
注意 react 是 peer dependency,安装前应确保已就位,文档中声明的 peer 范围为 ^17.0.0 || ^18.0.0 || ^19.0.0。
如果你更习惯 styled-components,则安装对应引擎替换包:
npm install @mui/system @mui/styled-engine-sc styled-components
pnpm add @mui/system @mui/styled-engine-sc styled-components
yarn add @mui/system @mui/styled-engine-sc styled-components
⚠️ 一个重要的适用前提(文档以 error 级提示强调):styled-components 不兼容服务端渲染(SSR)的 MUI 项目——babel-plugin-styled-components 无法处理 @mui 包内部的 styled() 工具。因此 SSR 项目被强烈建议使用 Emotion。
进一步阅读
本文围绕官方 Overview 展开,更多细节可继续阅读仓库内以下配套文档与源码:
- the
sxprop 完整文档:覆盖全部主题映射属性、数组覆盖、TypeScript 类型收窄与回调值迁移; - Usage 完整文档:含速记大全、响应式值、容器查询与更多代码演示;
- Installation 文档:双样式引擎安装与 SSR 注意事项;
- custom-components 文档:将
sx风格函数接入自定义组件的进阶做法; - 默认
sx配置字典:所有属性 → 主题令牌映射的一手来源; sx解析核心:理解断点展开、嵌套递归与容器查询排序的实现细节;Box组件源码 与Container组件源码:查看这两个最常用包装组件的最小实现。
简言之,MUI System 通过 sx 把“CSS 编写”“主题令牌取值”“响应式断点”三件事合并进一个表达力极强的 prop 中,是快速落地自定义设计与统一视觉体系时最值得优先考虑的基础设施。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00