MUI Version Migration
v4 → v5 Migration
Package Renames
# 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
Run the v5 Codemod
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.
Import Path Changes
// 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';
createTheme Changes
// 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',
},
},
},
},
});
makeStyles → styled / sx
This is the most impactful change. JSS (makeStyles, withStyles) is replaced by Emotion.
Pattern 1: sx prop (simple, inline styles)
// 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>
);
}
Pattern 2: styled() (reusable styled components)
// 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>;
}
Pattern 3: tss-react (drop-in makeStyles replacement)
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>;
}
withStyles → styled
// 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
color Prop Changes
// 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>
System Props Removed from Most Components
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>
Lab Component Moves
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 |
v4 → v5 Breaking Changes Checklist
- [ ] Replace
@material-ui/*with@mui/* - [ ] Add
@emotion/reactand@emotion/styledpeer deps - [ ] Replace
createMuiThemewithcreateTheme - [ ] Replace
theme.overrides+theme.propswiththeme.components - [ ] Replace all
makeStyles/withStyles(JSS) withstyled/sx/tss-react - [ ] Remove
color="default"— usecolor="inherit"or omit - [ ] Update Lab imports to core for graduated components
- [ ] Replace
<Hidden>withsx={{ display: { xs: 'none', sm: 'block' } }} - [ ] Update
Boxsystem shorthand keys (some changed) - [ ] Check all
classNameoverrides — internal class names changed in v5
v5 → v6 Migration
Run the v6 Codemod
# 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 (Major Breaking Change)
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>
Grid v2 — Offset
// 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>
Slots Pattern Standardized
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' } }}
/>
Pigment CSS (Experimental in v6)
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'.
v5 → v6 Breaking Changes Checklist
- [ ] Update all packages:
npm install @mui/material@6 @mui/x-date-pickers@7 @mui/x-data-grid@7 - [ ] Replace Grid v1 props (
item,xs/sm/md) with Grid v2sizeprop - [ ] Replace
components/componentsPropswithslots/slotPropsin all MUI X components - [ ] Check
@mui/x-date-pickerspeer dep: now requiresdayjsor another adapter explicitly - [ ]
useMediaQuerynow requires athemeargument in some contexts — verify usages - [ ]
Stackdividerspacing changed — verify visual output - [ ] Review
@mui/x-data-gridAPI: some column definition fields changed
Incremental Migration Strategy
For large codebases (100+ components), do not migrate everything at once.
Phase 1: Package and Config (1 day)
# 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
Phase 2: Theme Migration (1–2 days)
// Migrate theme file first — everything depends on it
// Old: createMuiTheme with overrides/props
// New: createTheme with components.MuiXxx.styleOverrides/defaultProps
Phase 3: Component-by-Component (ongoing)
Prioritize components that are:
- Used most frequently (Button, TextField, etc.)
- Already have a clear
sx-based replacement - Self-contained (not deeply composed)
// Migrate one component at a time:
// 1. Remove makeStyles import
// 2. Replace className={classes.xxx} with sx={...} or styled()
// 3. Run tests
// 4. Commit
Phase 4: Remove JSS Entirely
# Once all makeStyles are removed:
npm uninstall @material-ui/styles
Coexistence Strategy (v4 + v5 during migration)
// 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 → styled/sx Conversion Reference
| 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') })) |
Common Post-Migration Issues
Styles not applying:
- Check that
CssBaselineis insideThemeProvider - Confirm
@emotion/reactand@emotion/styledare installed - Ensure no JSS
StylesProvideris conflicting
TypeScript errors after codemod:
makeStylesreturn type changed —classesis now in a{ classes }object with tss-reactThemeimport moved from@material-ui/coreto@mui/material/styles- Some prop types narrowed (e.g.,
colorno longer accepts'default')
SSR style flash (FOUC):
- Missing Emotion cache setup — see the performance skill for full SSR config
- Ensure
createEmotionCache()is called per request, not once globally
Grid layout broken after v6 upgrade:
- The
itemprop is removed — addsizeprop to every grid item - Run
npx @mui/codemod v6.0.0/grid-v2-props src/to automate this