plugins/mui-expert/skills/sx-styled/SKILL.md
MUI sx prop and styled() API for component styling
npx skillsauth add markus41/claude sx-styledInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
MUI provides two primary APIs for styling components: the sx prop (inline, one-off
styles with full theme access) and the styled() function (reusable styled components
built on Emotion). Choose based on reuse: sx for single-use overrides, styled() for
components used more than once.
The sx prop accepts a superset of CSS where values can reference theme tokens, respond
to breakpoints, and use shorthand aliases. It is available on every MUI component and on
the Box primitive.
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
// Plain CSS properties — camelCase
<Box sx={{ backgroundColor: 'white', borderRadius: 2, boxShadow: 3 }}>
content
</Box>
// Theme token references
<Box sx={{ color: 'primary.main', bgcolor: 'background.paper' }} />
// Typography variants
<Box sx={{ typography: 'h4' }}>Heading text</Box>
MUI maps single-letter aliases to CSS properties. These only work inside sx (and
styled with the system utilities), not in plain Emotion.
<Box
sx={{
m: 2, // margin: theme.spacing(2)
p: 3, // padding: theme.spacing(3)
mx: 'auto', // marginLeft + marginRight: auto
my: 1, // marginTop + marginBottom
px: 2, // paddingLeft + paddingRight
py: 1, // paddingTop + paddingBottom
mt: 4, // marginTop
mb: 2, // marginBottom
ml: 1, // marginLeft
mr: 1, // marginRight
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-between',
gap: 2, // gap: theme.spacing(2)
width: 1, // width: 100% (fractions map to %)
height: '100vh',
}}
/>
Use the callback form (theme) => ({...}) when you need theme values that cannot be
expressed as token strings, such as palette computed colors or custom spacing math.
<Box
sx={(theme) => ({
backgroundColor: theme.palette.mode === 'dark'
? theme.palette.grey[900]
: theme.palette.grey[100],
padding: theme.spacing(2, 3), // shorthand: vertical, horizontal
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
transition: theme.transitions.create(['background-color'], {
duration: theme.transitions.duration.short,
}),
})}
/>
Pass an object keyed by breakpoint names. Values are applied from the named breakpoint upward (mobile-first).
<Box
sx={{
width: {
xs: '100%', // 0px+
sm: '80%', // 600px+
md: '60%', // 900px+
lg: '50%', // 1200px+
},
fontSize: { xs: 14, md: 16, lg: 18 },
display: { xs: 'block', md: 'flex' },
flexDirection: { xs: 'column', md: 'row' },
gap: { xs: 1, md: 2 },
}}
/>
Arrays map values to breakpoints in order [xs, sm, md, lg, xl]. Use null to skip
a breakpoint without changing the value.
<Box
sx={{
padding: [1, 2, 3], // xs=1, sm=2, md=3
fontSize: [12, null, 16], // xs=12, sm unchanged, md=16
display: ['block', 'flex'], // xs=block, sm+=flex
}}
/>
The sx prop supports any CSS selector string as a key, enabling hover states,
focus-visible, and targeting MUI's internal slot class names.
<Button
sx={{
'&:hover': {
backgroundColor: 'primary.dark',
transform: 'translateY(-1px)',
},
'&:active': {
transform: 'translateY(0)',
},
'&:focus-visible': {
outline: '3px solid',
outlineColor: 'primary.light',
},
// Target MUI internal slot classes
'& .MuiButton-startIcon': {
marginRight: 0.5,
},
// Target child elements
'& span': {
fontWeight: 700,
},
// Sibling state
'&.Mui-disabled': {
opacity: 0.5,
},
}}
>
Click me
</Button>
styled() is the Emotion styled function extended with MUI's theme and system
shorthands. Use it to create reusable, named components.
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
const HeroSection = styled(Box)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: theme.spacing(8, 2),
backgroundColor: theme.palette.background.default,
[theme.breakpoints.up('md')]: {
flexDirection: 'row',
padding: theme.spacing(12, 4),
},
}));
// Usage
<HeroSection component="section">
<h1>Welcome</h1>
</HeroSection>
Accept custom props to drive conditional styles. Use TypeScript generics to type them.
interface CardContainerProps {
variant?: 'elevated' | 'outlined' | 'filled';
selected?: boolean;
}
const CardContainer = styled(Box, {
// Prevent non-HTML props from being forwarded to the DOM element
shouldForwardProp: (prop) => prop !== 'variant' && prop !== 'selected',
})<CardContainerProps>(({ theme, variant = 'elevated', selected }) => ({
borderRadius: theme.shape.borderRadius * 2,
padding: theme.spacing(2),
cursor: 'pointer',
transition: theme.transitions.create(['box-shadow', 'border-color'], {
duration: theme.transitions.duration.short,
}),
...(variant === 'elevated' && {
boxShadow: selected ? theme.shadows[8] : theme.shadows[1],
'&:hover': { boxShadow: theme.shadows[4] },
}),
...(variant === 'outlined' && {
border: `1px solid`,
borderColor: selected
? theme.palette.primary.main
: theme.palette.divider,
boxShadow: 'none',
'&:hover': { borderColor: theme.palette.primary.light },
}),
...(variant === 'filled' && {
backgroundColor: selected
? theme.palette.primary.light
: theme.palette.action.hover,
boxShadow: 'none',
}),
}));
// Usage
<CardContainer variant="outlined" selected={isActive} onClick={handleClick}>
{children}
</CardContainer>
Always declare shouldForwardProp for custom boolean or string props to prevent React
warnings about unknown DOM attributes.
import { styled } from '@mui/material/styles';
import Button from '@mui/material/Button';
const GradientButton = styled(Button, {
shouldForwardProp: (prop) => prop !== 'gradient',
})<{ gradient?: boolean }>(({ theme, gradient }) => ({
...(gradient && {
background: `linear-gradient(45deg, ${theme.palette.primary.main} 30%, ${theme.palette.secondary.main} 90%)`,
color: theme.palette.common.white,
'&:hover': {
background: `linear-gradient(45deg, ${theme.palette.primary.dark} 30%, ${theme.palette.secondary.dark} 90%)`,
},
}),
}));
const PrimaryCard = styled(CardContainer)({
borderTop: '4px solid',
borderTopColor: 'primary.main',
});
| Scenario | Recommendation |
|---|---|
| One-off style on a single instance | sx prop |
| Same styles used on 2+ instances | styled() |
| Styles driven by custom props | styled() with shouldForwardProp |
| Overriding a MUI component globally | Theme components.MuiXxx.styleOverrides |
| Dynamic styles based on component state | sx callback or styled() with props |
| Performance-sensitive render-heavy list | styled() (styles computed once) |
| Quick prototype / layout tweak | sx prop |
The sx prop generates a new class name on every render when its value object changes
identity. For components that render frequently (virtualized lists, animated items),
prefer styled() or memoize the sx object.
// Bad — new object reference every render
function ListItem({ item }) {
return (
<Box sx={{ padding: 2, color: item.active ? 'primary.main' : 'text.primary' }}>
{item.label}
</Box>
);
}
// Better — stable reference for static parts, sx only for dynamic
const ItemBase = styled(Box)(({ theme }) => ({
padding: theme.spacing(2),
}));
function ListItem({ item }) {
return (
<ItemBase sx={{ color: item.active ? 'primary.main' : 'text.primary' }}>
{item.label}
</ItemBase>
);
}
// Alternative — useMemo for complex dynamic sx
function ListItem({ item, index }) {
const sxStyles = React.useMemo(() => ({
padding: 2,
color: item.active ? 'primary.main' : 'text.primary',
animationDelay: `${index * 50}ms`,
}), [item.active, index]);
return <Box sx={sxStyles}>{item.label}</Box>;
}
<Box
sx={{
bgcolor: (theme) =>
theme.palette.mode === 'dark' ? 'grey.900' : 'grey.50',
color: 'text.primary',
}}
/>
Pass an array of sx values to compose styles. Falsy entries are skipped.
<Box
sx={[
{ padding: 2, borderRadius: 1 },
isHighlighted && { bgcolor: 'warning.light' },
isDisabled && { opacity: 0.5, pointerEvents: 'none' },
]}
/>
const FullBleed = styled(Box)(({ theme }) => ({
width: '100vw',
position: 'relative',
left: '50%',
right: '50%',
marginLeft: '-50vw',
marginRight: '-50vw',
backgroundColor: theme.palette.primary.main,
padding: theme.spacing(4, 0),
}));
development
Enhanced plan-authoring skill with Pre-Writing context gathering, task metadata, non-TDD templates, Red Flags, telemetry, and an automated plan linter. Use when you have a spec or requirements for a multi-step task, before touching code.
tools
Documentation intelligence engine with graph-based API docs, algorithm library, and drift detection
tools
Ultraplan cloud planning — kick off a plan in the cloud from your terminal, review and revise in the browser, then execute remotely or send back to CLI
tools
--- name: mcp description: Configure MCP servers for Claude Code — stdio vs HTTP, authentication, Tools/Resources/Prompts distinction, channels (CI webhook, mobile relay, Discord bridge, fakechat), and cost of always-loaded tools. Use this skill whenever adding an MCP server, debugging connection issues, choosing between MCP Tools vs Prompts vs Resources, installing channel servers, or managing .mcp.json. Triggers on: "MCP server", "mcp config", "add Obsidian MCP", "install context7", "channels"