From mui-expert
MUI version migration guides — v4→v5 and v5→v6 with codemods and patterns
npx claudepluginhub markus41/claude --plugin mui-expertThis skill is limited to using the following tools:
```bash
Provides Ktor server patterns for routing DSL, plugins (auth, CORS, serialization), Koin DI, WebSockets, services, and testApplication testing.
Conducts multi-source web research with firecrawl and exa MCPs: searches, scrapes pages, synthesizes cited reports. For deep dives, competitive analysis, tech evaluations, or due diligence.
Provides demand forecasting, safety stock optimization, replenishment planning, and promotional lift estimation for multi-location retailers managing 300-800 SKUs.
# Uninstall v4 packages
npm uninstall @material-ui/core @material-ui/icons @material-ui/lab @material-ui/system
# Install v5 packages
npm install @mui/material @mui/icons-material @mui/lab @mui/system
npm install @emotion/react @emotion/styled # new required peer deps
# If using styled-components instead of emotion (less common):
npm install @mui/material @mui/styled-engine-sc styled-components
The codemod handles ~80% of the mechanical changes automatically.
# Run the v5 preset-safe codemod (safe subset — no breaking changes)
npx @mui/codemod v5.0.0/preset-safe src/
# Run on a single file
npx @mui/codemod v5.0.0/preset-safe src/components/MyComponent.tsx
# Run individual transforms
npx @mui/codemod v5.0.0/component-rename-prop src/
npx @mui/codemod v5.0.0/moved-lab-modules src/
npx @mui/codemod v5.0.0/optimal-imports src/
After running the codemod, review the diff carefully — some transformations may need manual adjustment.
// v4
import { makeStyles } from '@material-ui/core/styles';
import { createMuiTheme } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
// v5
import { styled } from '@mui/material/styles'; // replaces makeStyles
import { createTheme } from '@mui/material/styles'; // createMuiTheme → createTheme
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
// v4
import { createMuiTheme } from '@material-ui/core/styles';
const theme = createMuiTheme({
palette: {
primary: { main: '#1976d2' },
},
overrides: { // v4: overrides
MuiButton: {
root: { textTransform: 'none' },
},
},
props: { // v4: props
MuiButton: { disableRipple: true },
},
});
// v5
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
palette: {
primary: { main: '#1976d2' },
},
components: { // v5: components (combines overrides + props)
MuiButton: {
defaultProps: { // was: props
disableRipple: true,
},
styleOverrides: { // was: overrides
root: {
textTransform: 'none',
},
},
},
},
});
This is the most impactful change. JSS (makeStyles, withStyles) is replaced by Emotion.
// v4 with makeStyles
const useStyles = makeStyles((theme) => ({
root: {
padding: theme.spacing(2),
backgroundColor: theme.palette.background.paper,
borderRadius: theme.shape.borderRadius,
},
}));
function MyComponent() {
const classes = useStyles();
return <div className={classes.root}>Content</div>;
}
// v5 with sx prop
function MyComponent() {
return (
<Box
sx={{
p: 2,
bgcolor: 'background.paper',
borderRadius: 1,
}}
>
Content
</Box>
);
}
// v4 with makeStyles + clsx
const useStyles = makeStyles((theme) => ({
card: {
padding: theme.spacing(3),
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius * 2,
},
cardHighlighted: {
borderColor: theme.palette.primary.main,
backgroundColor: theme.palette.primary.light,
},
}));
function MyCard({ highlighted }: { highlighted: boolean }) {
const classes = useStyles();
return (
<div className={clsx(classes.card, { [classes.cardHighlighted]: highlighted })}>
Content
</div>
);
}
// v5 with styled()
import { styled } from '@mui/material/styles';
const StyledCard = styled('div', {
shouldForwardProp: (prop) => prop !== 'highlighted',
})<{ highlighted?: boolean }>(({ theme, highlighted }) => ({
padding: theme.spacing(3),
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius * 2,
...(highlighted && {
borderColor: theme.palette.primary.main,
backgroundColor: theme.palette.primary.light,
}),
}));
function MyCard({ highlighted }: { highlighted: boolean }) {
return <StyledCard highlighted={highlighted}>Content</StyledCard>;
}
For large codebases where a full migration is impractical, tss-react provides a near-identical API:
npm install tss-react @emotion/react
// Minimal diff from v4 makeStyles
import { makeStyles } from 'tss-react/mui';
const useStyles = makeStyles()((theme) => ({
root: {
padding: theme.spacing(2),
backgroundColor: theme.palette.background.paper,
},
}));
function MyComponent() {
const { classes } = useStyles();
return <div className={classes.root}>Content</div>;
}
// v4
const StyledButton = withStyles((theme) => ({
root: {
margin: theme.spacing(1),
},
label: {
color: theme.palette.primary.main,
},
}))(Button);
// v5
const StyledButton = styled(Button)(({ theme }) => ({
margin: theme.spacing(1),
'& .MuiButton-label': { // target internal class
color: theme.palette.primary.main,
},
}));
// Or better — use sx prop on Button directly
// v4 — color accepted any string
<Button color="default">Default</Button>
// v5 — 'default' removed; use 'inherit' or omit color
<Button>Default</Button>
<Button color="inherit">Inherit parent color</Button>
In v5, system props (like mt, p, bgcolor) work on Box and Stack but not on
all components. Use the sx prop instead.
// v4 — system props on Typography
<Typography mt={2} color="primary.main">Text</Typography>
// v5 — use sx prop
<Typography sx={{ mt: 2, color: 'primary.main' }}>Text</Typography>
Several components graduated from @mui/lab to @mui/material:
| v4 lab | v5 core |
|---|---|
@material-ui/lab/Alert | @mui/material/Alert |
@material-ui/lab/Autocomplete | @mui/material/Autocomplete |
@material-ui/lab/Pagination | @mui/material/Pagination |
@material-ui/lab/Rating | @mui/material/Rating |
@material-ui/lab/Skeleton | @mui/material/Skeleton |
@material-ui/lab/SpeedDial | @mui/material/SpeedDial |
@material-ui/lab/ToggleButton | @mui/material/ToggleButton |
@material-ui/* with @mui/*@emotion/react and @emotion/styled peer depscreateMuiTheme with createThemetheme.overrides + theme.props with theme.componentsmakeStyles/withStyles (JSS) with styled/sx/tss-reactcolor="default" — use color="inherit" or omit<Hidden> with sx={{ display: { xs: 'none', sm: 'block' } }}Box system shorthand keys (some changed)className overrides — internal class names changed in v5# Run preset-safe (recommended first step)
npx @mui/codemod v6.0.0/preset-safe src/
# Run on specific directories
npx @mui/codemod v6.0.0/preset-safe src/components/ src/pages/
# Individual transforms (if preset-safe missed something)
npx @mui/codemod v6.0.0/grid-v2-props src/
npx @mui/codemod v6.0.0/sx-prop src/
Grid v2 is the default Grid in v6. The item prop is removed; use size instead.
The xs, sm, md, lg, xl props are replaced by a single size prop with an object.
// v5 Grid
import Grid from '@mui/material/Grid';
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={4}>
<Card />
</Grid>
<Grid item xs={12} sm={6} md={8}>
<Content />
</Grid>
</Grid>
// v6 Grid (Grid v2) — same import path, new API
import Grid from '@mui/material/Grid';
<Grid container spacing={2}>
<Grid size={{ xs: 12, sm: 6, md: 4 }}>
<Card />
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 8 }}>
<Content />
</Grid>
</Grid>
// Shorthand for full-width only
<Grid size={12}>Full width</Grid>
// Auto-grow
<Grid size="grow">Fills remaining space</Grid>
// v5 — offset via empty grid items or negative margins
<Grid item xs={4} /> // spacer
<Grid item xs={8}><Content /></Grid>
// v6 — offset prop
<Grid size={8} offset={4}><Content /></Grid>
// Responsive offset
<Grid size={{ xs: 12, md: 8 }} offset={{ md: 4 }}>
<Content />
</Grid>
In v6, the slots/slotProps API is standardized across all MUI X components
(DatePicker, DataGrid, Charts). The old components/componentsProps API is removed.
// v5 (deprecated in v6)
<DatePicker
components={{ TextField: CustomTextField }}
componentsProps={{ textField: { variant: 'outlined' } }}
/>
// v6 (required)
<DatePicker
slots={{ textField: CustomTextField }}
slotProps={{ textField: { variant: 'outlined' } }}
/>
v6 introduces optional Pigment CSS — a build-time CSS extraction alternative to Emotion. It enables zero-runtime styling (faster SSR, smaller bundles).
# Install Pigment CSS (optional — still experimental in v6)
npm install @mui/material-pigment-css @pigment-css/react
# Next.js setup
npm install @pigment-css/nextjs-plugin
// next.config.js
const { withPigment } = require('@pigment-css/nextjs-plugin');
module.exports = withPigment({
// your next config
}, {
theme: require('./src/theme').default,
});
With Pigment CSS, styled() and sx are processed at build time — no Emotion runtime.
Server components can use styled components directly without 'use client'.
npm install @mui/material@6 @mui/x-date-pickers@7 @mui/x-data-grid@7item, xs/sm/md) with Grid v2 size propcomponents/componentsProps with slots/slotProps in all MUI X components@mui/x-date-pickers peer dep: now requires dayjs or another adapter explicitlyuseMediaQuery now requires a theme argument in some contexts — verify usagesStack divider spacing changed — verify visual output@mui/x-data-grid API: some column definition fields changedFor large codebases (100+ components), do not migrate everything at once.
# 1. Update packages
npm uninstall @material-ui/core @material-ui/icons
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled
# 2. Run codemods
npx @mui/codemod v5.0.0/preset-safe src/
# 3. Fix obvious import errors
# 4. Run the app; expect red errors — that's normal at this stage
// Migrate theme file first — everything depends on it
// Old: createMuiTheme with overrides/props
// New: createTheme with components.MuiXxx.styleOverrides/defaultProps
Prioritize components that are:
sx-based replacement// Migrate one component at a time:
// 1. Remove makeStyles import
// 2. Replace className={classes.xxx} with sx={...} or styled()
// 3. Run tests
// 4. Commit
# Once all makeStyles are removed:
npm uninstall @material-ui/styles
// Temporarily keep both packages during migration
// @material-ui/core for unmigrated components
// @mui/material for migrated components
// Wrap each style engine separately
import { StylesProvider } from '@material-ui/core/styles'; // v4 JSS
import { ThemeProvider } from '@mui/material/styles'; // v5 Emotion
function App() {
return (
<StylesProvider injectFirst>
<ThemeProvider theme={v5Theme}>
{/* migrated components use v5 theme */}
{/* unmigrated components use v4 styles */}
<Router />
</ThemeProvider>
</StylesProvider>
);
}
| makeStyles pattern | v5 equivalent |
|---|---|
padding: theme.spacing(2) | sx={{ p: 2 }} |
margin: theme.spacing(1, 2) | sx={{ my: 1, mx: 2 }} |
color: theme.palette.primary.main | sx={{ color: 'primary.main' }} |
backgroundColor: theme.palette.grey[100] | sx={{ bgcolor: 'grey.100' }} |
display: 'flex' | sx={{ display: 'flex' }} |
[theme.breakpoints.up('sm')]: { ... } | sx={{ md: { ... } }} |
'&:hover': { ... } | sx={{ '&:hover': { ... } }} |
'& .MuiButton-root': { ... } | sx={{ '& .MuiButton-root': { ... } }} |
theme.transitions.create('opacity') | styled()(({ theme }) => ({ transition: theme.transitions.create('opacity') })) |
Styles not applying:
CssBaseline is inside ThemeProvider@emotion/react and @emotion/styled are installedStylesProvider is conflictingTypeScript errors after codemod:
makeStyles return type changed — classes is now in a { classes } object with tss-reactTheme import moved from @material-ui/core to @mui/material/stylescolor no longer accepts 'default')SSR style flash (FOUC):
createEmotionCache() is called per request, not once globallyGrid layout broken after v6 upgrade:
item prop is removed — add size prop to every grid itemnpx @mui/codemod v6.0.0/grid-v2-props src/ to automate this