Material UI 中 MUI System 的 sx 属性使用指南:从样式方案选型到响应式主题实践
本篇技术指南以 MUI System 的入门文档为核心,讲解如何在 Material UI 项目中利用 sx 属性在组件内部直接书写样式、按主题取值并实现响应式布局。读完本文你将掌握:为什么用 sx 代替 styled-components、sx 能在哪些组件上使用、如何利用主题设计令牌与简写、如何使用对象/数组两种断点语法以及 v6 起新增的容器查询与自定义断点方案,并了解其性能开销与适用场景。
MUI System 是什么:用 sx 代替不必要的 styled 样板代码
MUI System 是一套提供给样式与布局的 CSS 工具集合。它的 sx 属性让你不必编写多余的 styled-component 代码,而是把样式直接定义在组件内部——这对一次性的、带定制设计的组件尤其有用。
文档 usage.md 用一个"数据统计卡片"(Sessions 卡片)分别展示了两条实现路径的差异:
方式一:使用 styled-components API
需要为每个元素逐一声明独立的 styled 组件,并逐个消费 theme:
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-alignment: 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>
);
仅仅为了排版一个卡片就需要 6 个具名的 styled 组件,视觉与 DOM 结构被割裂在文件不同位置。
方式二:使用 MUI System 的 sx
同一份 UI,用 Box 搭配 sx 全部收敛在 JSX 内,主题取值与布局一目了然:
<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>
同样的最终样式,sx 版本更贴近"就地定制"的心智模型。仓库中的真实示例可见 Why.js,它把卡片套上 border、borderColor: 'divider'、borderRadius: 2 等细节,完整还原了在线演示效果。
核心机制:sx 是 CSS 的超集,并自动从主题取值
MUI System 的核心工具就是 sx 属性——它提供一种快速高效的方式,把正确的设计令牌直接应用到一个 React 元素上。
sx 是 CSS 的超集:它既包含全部 CSS 属性与选择器,还额外提供自定义属性(如 bgcolor、mx),并依据所用 CSS 属性把值**直接映射到主题(theme)**上。同时,它通过引用主题中定义的断点(breakpoints),简化了响应式值的书写。
从类型定义看,sx 接受的值在 styleFunctionSx.d.ts 中被归纳为 SystemStyleObject:它可以是标准 CSS 属性值、响应式值(T | 数组 | 对象)、CSS 伪类(:hover 等)、嵌套选择器与 CSS 变量的映射,也可以是一个接收 theme 并返回样式的函数。而 SxProps 还支持传入函数或数组(ReadonlyArray<boolean | SystemStyleObject | (theme) => ...>),这让条件样式与主题动态取值成为可能。
sx 的值之所以能"命中"主题对应键,是因为底层有一份可查的配置表:defaultSxConfig.ts。例如:
borderColor、color、bgcolor(会映射为 CSS 的backgroundColor)→themeKey: 'palette';borderRadius→themeKey: 'shape.borderRadius';boxShadow→themeKey: 'shadows';zIndex→themeKey: 'zIndex';fontFamily、fontSize、fontWeight→themeKey: 'typography';p/px/py、m/mx/my及全称padding*/margin*→ 走 spacing 的padding/margin计算函数。
也就是说,sx 的作用相当于替你完成了"从 CSS 属性名 → 找主题键 → 解析取值 → 生成 CSS"这一过程。
只用 Box 也能搭建复杂响应式 UI
sx 不仅能表达简单内联样式,也可以独立构造完整、复杂的 UI 组件。在线演示 Demo.js 仅靠一个外层 Box + 若干内层 Box 就渲染出一张"房产卡片"(含图片、地址、价格、置信度徽标),并且调整浏览器窗口宽度即可看到断点生效:
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', md: 'row' }, // 窄屏纵向、宽屏横向
alignItems: 'center',
bgcolor: 'background.default',
border: '1px solid',
borderColor: 'divider',
borderRadius: 2,
overflow: 'clip',
}}
>
<Box
component="img"
sx={{
height: 233,
width: 350,
maxHeight: { xs: 233, md: 167 },
maxWidth: { xs: 350, md: 250 },
}}
alt="The house from the offer."
src="..."
/>
{/* 文本信息区与徽标区同样由 Box + sx 完成 */}
</Box>
这里已经能看到 sx 的几个关键手法:flexDirection: { xs: 'column', md: 'row' } 的对象式断点、component="img" 改变渲染元素、以及 borderColor: 'divider' 这类从主题取色的写法。所有元素共用同一个样式解析管线,无需任何自定义 CSS 文件。
什么时候该用 sx,什么时候改用 styled-components
sx 最适合给自定义组件应用一次性样式;而 styled-components API 则更适合构建需要在多种上下文复用的组件——例如同一组件在应用的不同位置出现、需要支持不同的 props 组合时。
性能权衡:CSS-in-JS 的成本
MUI System 依赖 CSS-in-JS 运行时,同时兼容 Emotion 与 styled-components 两种底层引擎。
优点(Pros)
- 语法友好:
sx使用 CSS 超集,已熟悉 CSS 即可立即上手;同时提供(可选的)简写,如p、m、bgcolor、borderRadius等,愿意花少量精力学习即可显著节省书写时间。这些简写文档化在各 Style utilities 页面中。 - 按需输出(auto-purge):System 会自动清理,只把页面真正用到的 CSS 发送给客户端。初始体积成本是固定的——你新增更多 CSS 属性,并不会让体积继续变大。你需要支付的主要是
@emotion/react与@mui/system的成本,总计约 ~15 kB gzipped;如果项目已经在使用 Material UI 这类 MUI Core 组件库,则没有额外开销。
缺点(Cons)
运行时性能会有所牺牲。MUI 官方文档提供了一组基准对照(数值为归一化的渲染耗时,越小越快):
| Benchmark 场景 | 代码片段 | 归一化时间 |
|---|---|---|
| a. 渲染 1,000 个原生元素 | <div className="…"> |
100ms |
| b. 渲染 1,000 个组件 | <Div> |
112ms |
| c. 渲染 1,000 个 styled 组件 | <StyledDiv> |
181ms |
| d. 渲染 1,000 个 Box | <Box sx={…}> |
296ms |
对绝大多数使用场景而言,这个开销完全够用;但当性能成为关键时也有简单的应对办法。例如渲染超长列表时,可以用一个 CSS 子选择器让整个列表只做"一次样式注入"——外层容器用 sx(对应 d 场景),列表里的每一项直接用原生元素(对应 a 场景),从而把每次渲染的样式解析开销摊平为常数。
API 取舍:职责分离
sx 的存在让 CSS 工具职能与组件业务逻辑保持分离。举例来说,组件上的 color prop 与 CSS 的 color 属性含义并不相同——Button 的 color prop 会同时影响 hover、focus 等多个状态。MUI 组件正是通过 sx 属性来应用 System 能力,而组件的专有 props 保持聚焦在"已文档化的组件行为"上,从而避免与原生属性或自定义属性产生冲突。
sx 可以用在哪四处
sx 在四种位置上均可使用:
- MUI Core 组件:所有 Material UI 组件都支持
sxprop。 - Box 组件:
Box是专为接入sx而生的轻量组件,既可当工具组件使用,也可作为其他组件的容器;默认渲染一个<div>(对应源码实现位于 Box)。 - 自定义组件:除 MUI System 组件外,可用
@mui/material/styles导出的styled工具为自己的组件开启sx:
import { styled } from '@mui/material/styles';
const Div = styled('div')``;
从源码看,styled('div') 返回的组件已经带上了 sx 解析能力,因此 sx 就能在任意用 styled 包装的自定义组件上直接工作。
- 配合 Babel 插件的任意元素:社区围绕"让普通 DOM 元素也支持 sx"的讨论见 MUI 仓库 issue #23220(官方文档原始出处即该 issue)。
如何用好 sx:主题令牌、简写与超集语法
从主题中取设计令牌
不同 CSS(及自定义)属性如何映射到主题键、取值规则细节,可查阅 System properties 说明页;其底层映射的实现即上文提到的 defaultSxConfig.ts。总的原则是:凡是能在主题某键下找到的值,写进 sx 都会被自动解析。
简写(Shorthands)
官方文档对大量 CSS 属性提供了简写(详见各自 Style utilities 页面)。下面这个例子集中展示了最常见的几种简写及其背后对应的主题解析结果:
<Box
sx={{
boxShadow: 1, // theme.shadows[1]
color: 'primary.main', // theme.palette.primary.main
m: 1, // margin: theme.spacing(1)
p: {
xs: 1, // [theme.breakpoints.up('xs')]: { padding: theme.spacing(1) }
},
zIndex: 'tooltip', // theme.zIndex.tooltip
}}
>
这些简写完全是可选项——它们能帮你节省时间,但并不是非用不可;你完全可以在同一对象里混用全称 CSS 属性。
CSS 超集:伪类、媒体查询与嵌套选择器
sx 原生支持子选择器、伪类、媒体查询、原始 CSS 值等标准 CSS 语法:
- 伪类选择器:
<Box
sx={{
// some styles
':hover': {
boxShadow: 6,
},
}}
>
- 媒体查询:
<Box
sx={{
// some styles
'@media print': {
width: 300,
},
}}
>
- 嵌套子选择器:
<Box
sx={{
// some styles
'& .ChildSelector': {
bgcolor: 'primary.main',
},
}}
>
这也是 styleFunctionSx.d.ts 中 CSSPseudoSelectorProps(覆盖全部 CSS 伪类)与 CSSSelectorObject/CSSSelectorObjectOrCssVariables(覆盖任意嵌套选择器与 CSS 变量)类型的来源——类型层面已经保证这些键可被合法书写。
响应式值:对象、数组与自定义断点
sx 把响应式断点的定义简化为两种写法:对象或数组。
断点写成对象
用断点名作为键。注意:某个断点下的属性会同时作用于该断点及所有更大的断点——例如 width: { lg: 100 } 等价于 theme.breakpoints.up('lg'),lg 及以上宽度都会是 100px。
<Box
sx={{
width: {
xs: 100, // theme.breakpoints.up('xs')
sm: 200, // theme.breakpoints.up('sm')
md: 300, // theme.breakpoints.up('md')
lg: 400, // theme.breakpoints.up('lg')
xl: 500, // theme.breakpoints.up('xl')
},
}}
>
This box has a responsive width.
</Box>
默认断点键为 xs、sm、md、lg、xl(可在 createBreakpoints.ts 中确认 breakpointKeys 的定义)。完整可运行示例见 BreakpointsAsObject.js。
v6 起:容器查询(container queries)简写
从 v6 开始,对象结构支持以 @ 开头的容器查询简写。使用前请先确认目标浏览器对 CSS 容器查询的支持情况。其语法为 @{breakpoint}/{container}:
- breakpoint:一个代表
px的数字,或一个断点键(默认断点如sm、md、lg、xl),或任何合法的 CSS 值(例如40em); - container(可选):containment context 的名称。
例如 ContainerQueries.js 通过外层 Box 声明 containerType: 'inline-size'(并可横向拖拽 resize: 'horizontal' 改变宽度),内层卡片据此做容器级响应:
<Box
sx={{
overflow: 'auto',
resize: 'horizontal',
width: 400,
maxWidth: '80%',
containerType: 'inline-size', // 声明容器查询上下文
}}
>
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', '@350': 'row' }, // 容器宽 >=350px 时切为行向
...
}}
>
<Box component="img" sx={{ maxWidth: { '@350': '36%', '@500': 240 }, ... }} />
...
</Box>
</Box>
源码中 cssContainerQueries 负责识别这类 @key/@数字 简写并转换为真正的容器查询媒体条件。实现层面与容器查询相关的解析逻辑可在 cssContainerQueries.ts 查看(其 isCqShorthand 会校验键是否以 @sm 等断点名或 @数字 开头)。
断点写成数组(从小到大)
另一种写法是从最小到最大按序排列的数组:
<Box sx={{ width: [100, 200, 300] }}>This box has a responsive width.</Box>
上面这行等价于三个递增断点的宽度。官方建议:只在主题断点数量有限(例如 3 个)时使用数组写法;需要定义多个断点时应改用对象 API。可运行示例见 BreakpointsAsArray.js。
数组中的空缺断点可用 null 跳过:
<Box sx={{ width: [null, null, 300] }}>This box has a responsive width.</Box>
含义是:前两个断点不设值,从第三个断点起 width: 300。
自定义断点
也可以自行命名断点并作为对象键使用。做法是通过 createTheme 覆盖 breakpoints.values,再用 ThemeProvider 注入:
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 让主题类型接受这些新键(声明默认断点为 false 即"移除",新断点为 true 即"新增"):
declare module '@mui/material/styles' {
interface BreakpointOverrides {
xs: false; // removes the `xs` breakpoint
sm: false;
md: false;
lg: false;
xl: false;
tablet: true; // adds the `tablet` breakpoint
laptop: true;
desktop: true;
}
}
主题取值函数:处理 sx 未原生支持的属性
如果某个 CSS 属性并非 MUI System 原生支持,无法直接取到主题,可以把值写成函数,在函数体内访问整个 theme 对象。例如让 borderColor 直接用函数返回主题色(ValueAsFunction.js):
<Box
sx={{
p: 1,
border: 1,
borderColor: (theme) => theme.palette.primary.main,
}}
>
Border color with theme value.
</Box>
结合 styleFunctionSx.d.ts 中 SystemCssProperties 的类型签名(T | ((theme: Theme) => T) | null),可以确认:几乎任何属性的值都可以替换成接收 theme 的回调,因此主题取值函数是 sx 相对普通 CSS 的超集能力的兜底通道。
总结:一条选型决策路径
- 需要给一个一次性自定义组件快速上样式 → 直接用 Material UI 组件的
sx或Box; - 需要从主题取色/取阴影/取间距/取字体权值 → 记住默认断点键与
defaultSxConfig的映射关系即可省去手写theme.*; - 需要响应式 → 优先对象断点(跨断点累积语义清晰),断点少且稳定时可用数组,v6 及以后还可在容器级用
@容器查询; - 组件需要被应用多处、承载多变体 props → 才值得升级为 styled-components;
- 追求运行时渲染性能的列表等热点 → 用"单点注入 + 原生元素"规避逐元素解析开销。
如需把 MUI System 作为独立包引入工程,可参考 安装指南;对 sx 属性的完整 API(对象数组混用、主题回调、透传等)可继续阅读 the-sx-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