Upgrade to v9
This guide explains how to upgrade from Material UI v7 to v9.
Start using the alpha release
In the package.json file, change the package version from latest to next.
-"@mui/material": "latest",
+"@mui/material": "next",
Using next ensures your project always uses the latest v9 pre-releases.
Alternatively, you can also target and fix it to a specific version, for example, 9.0.0-alpha.0.
Breaking changes
Since v9 is a new major release, it contains some changes that affect the public API. The steps you need to take to migrate from Material UI v7 to v9 are described below.
Backdrop
The Backdrop component no longer adds the aria-hidden="true" attribute to the Root slot by default.
Dialog & Modal
The disableEscapeKeyDown prop has been removed. The same behavior could be achieved
by checking the value of the reason argument in onClose:
const [open, setOpen] = React.useState(true);
- const handleClose = () => {
- setOpen(false);
- };
+ const handleClose = (_event: React.SyntheticEvent<unknown>, reason: string) => {
+ if (reason !== 'escapeKeyDown') {
+ setOpen(false);
+ }
+ };
return (
- <Dialog open={open} disableEscapeKeyDown onClose={handleClose}>
+ <Dialog open={open} onClose={handleClose}>
{/* ... */}
</Dialog>
);
The Modal change is the same.
ButtonBase
Click event propagation from Enter and Spacebar
When sending Enter and Spacebar keys on the ButtonBase or components that are composed from ButtonBase, the click event now bubbles to their ancestors.
Also, the event passed to the onClick prop is a MouseEvent instead of the KeyboardEvent captured
in the ButtonBase keyboard handlers. This is actually the expected behavior.
Event handlers on disabled non-native buttons
When ButtonBase renders a non-native element like a <span>, keyboard and click event handlers will no longer run when the component is disabled.
Replacing native button elements with non-interactive elements
The nativeButton prop is available on <ButtonBase> and all button-like components to ensure that they are rendered with the correct HTML attributes before hydration, for example during server-side rendering.
This should be specified when passing a React component to the component prop of a button-like component that either replaces the default rendered element:
- From a native
<button>to a non-interactive element like a<div>, or - From a non-button like a
<div>to a native<button>
const CustomButton = React.forwardRef(function CustomButton(props, ref) {
return <div ref={ref} {...props} />;
})
<Button component={CustomButton} nativeButton={false}>
OK
</Button>
A warning will be shown in development mode if the nativeButton prop is incorrectly omitted, or if the resolved element
does not match the value of the prop.
The prop can be used for: <ButtonBase>, <Button>, <Fab>, <IconButton>, <ListItemButton>, <MenuItem>, <StepButton>, <Tab>, <ToggleButton>, <AccordionSummary>, <BottomNavigationAction>, <CardActionArea>, <TableSortLabel> and <PaginationItem>.
Autocomplete
Listbox toggle on right click
The listbox does not toggle anymore when using right click on the input. The left click toggle behavior remains unchanged.
freeSolo type related changes
When the freeSolo prop is passed as true, the getOptionLabel and isOptionEqualToValue props
accept string as well for their option and, respectively, value arguments:
- isOptionEqualToValue?: (option: Value, value: Value) => boolean;
+ isOptionEqualToValue?: (
+ option: Value,
+ value: AutocompleteValueOrFreeSoloValueMapping<Value, FreeSolo>,
+ ) => boolean;
- getOptionLabel?: (option: Value | AutocompleteFreeSoloValueMapping<FreeSolo>) => string;
+ getOptionLabel?: (option: AutocompleteValueOrFreeSoloValueMapping<Value, FreeSolo>) => string;
For reference:
type AutocompleteFreeSoloValueMapping<FreeSolo> = FreeSolo extends true
? string
: never;
type AutocompleteValueOrFreeSoloValueMapping<Value, FreeSolo> = FreeSolo extends true
? Value | string
: Value;
Grid
The Grid component no longer supports system props.
Use the sx prop instead:
-<Grid mt={2} mr={1} />
+<Grid sx={{ mt: 2, mr: 1 }} />
This also fixes an issue where props like color were consumed by the Grid instead of being forwarded to the component rendered via the component prop:
// `color` is now correctly forwarded to Button
<Grid component={Button} color="secondary" variant="contained">
hello
</Grid>
GridLegacy
The GridLegacy component is removed, use the Grid component instead.
The main API differences are:
- The
itemprop is no longer needed. - The
xs,sm,md,lg,xlprops are replaced by thesizeprop.
-import Grid from '@mui/material/GridLegacy';
+import Grid from '@mui/material/Grid';
<Grid container spacing={2}>
- <Grid item xs={12} sm={6}>
+ <Grid size={{ xs: 12, sm: 6 }}>
...
</Grid>
</Grid>
See the Grid v2 migration guide for more details.
MuiGridLegacy has also been removed from the theme components types (ComponentsProps, ComponentsOverrides, and ComponentsVariants).
TablePagination numbers are formatted by default
Pagination numbers in TablePagination are now formatted using Intl.NumberFormat according to the locale.
For example, 103177 is displayed as 103,177 in en-US or 103.177 in de-DE.
To opt out of number formatting, provide a custom labelDisplayedRows function:
<TablePagination
labelDisplayedRows={({ from, to, count }) =>
`${from}–${to} of ${count !== -1 ? count : `more than ${to}`}`
}
/>
Or when using a locale:
import { enUS } from '@mui/material/locale';
const theme = createTheme(
{
palette: {
primary: { main: '#1976d2' },
},
},
enUS,
{
components: {
MuiTablePagination: {
defaultProps: {
labelDisplayedRows: ({ from, to, count }) =>
`${from}–${to} of ${count !== -1 ? count : `more than ${to}`}`,
},
},
},
},
);
TextField
When specifying <TextField select /> to render a <Select>, the underlying <InputLabel> renders a <div> instead of a native <label> element. This does not affect <InputLabel> on its own.
Theme
MuiTouchRipple has been removed from the theme components types (ComponentsProps, ComponentsOverrides, and ComponentsVariants).
TouchRipple has been an internal component since v5 and never consumed theme overrides or default props, so the types were misleading.
If you were using MuiTouchRipple in your theme, remove it and use global CSS with the MuiTouchRipple-* class names instead:
const theme = createTheme({
components: {
- MuiTouchRipple: {
- styleOverrides: {
- root: { color: 'red' },
- },
- },
+ MuiButtonBase: {
+ styleOverrides: {
+ root: {
+ '& .MuiTouchRipple-root': { color: 'red' },
+ },
+ },
+ },
},
});
JSDOM support
v9 removes all usage of process.env.NODE_ENV === 'test'. The NODE_ENV variable will exclusively be used for for tree-shaking. Our libraries have been updated to auto-detect DOM environments that don't support layout such as JSDOM and happy-dom through user agent sniffing.
Stepper, Step and StepButton
The Stepper and Step markups have changed to improve their semantics:
Stepperreturns a<ol>element instead of<div>.Stepreturns a<li>element instead of<div>.
The Stepper component now supports keyboard navigation when used with StepButton descendants. The navigation is implemented as a roving tabindex, supporting Arrow Keys as well as Home and End. Only one StepButton is focusable at a time, by having tabindex="0", while the rest all have tabindex="-1". Once selection is changed by Arrow Keys or Home / End, the tabindex value is also updated.
The markup for a Stepper with StepButton descendants has changed further to reflect this behavior. These changes apply on top of the tag changes described above.
The Stepper has:
- The
roleoftablist. - The
aria-orientationadded. The value is eitherhorizontalorverticaldepending on theorientationprop.
The StepButton has:
- The
roleoftab. - The
aria-currentchanged toaria-selected. The value istruewhen step is selected, andfalseotherwise. - The
aria-setsizeadded. The value is the total number of steps. - The
aria-posinsetadded. The value is the index of the step inside the list, 1-based.
Tabs
The tabindex attribute for each tab will be changed on Arrow Key or Home / End navigation. Previously, we only moved the focus on keyboard navigation. Now, we move the focus and also add the tabindex="0" to the focused element. The previously focused element will have its tabindex updated to -1 in order to keep only one focusable Tab at a time.
Selecting a Tab will update the focus and tabindex as before.
Menu and MenuList
The tabindex attribute for each menu item will be changed on Arrow Key, Home / End or Character Key navigation. Previously, we only moved the focus on keyboard navigation. Now, we move the focus and also add the tabindex="0" to the focused element. The previously focused element will have its tabindex updated to -1 in order to keep only one focusable MenuItem at a time.
This change also applies to the Menu since it uses MenuList.
Selecting a MenuItem will update the focus and tabindex as before.
The autoFocus prop in MenuList does not set tabindex="0" on the List component anymore. It will always stay as -1.
Deprecated APIs removed
APIs that were deprecated earlier have been removed in v9.
Autocomplete deprecated props removed
Use the autocomplete-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/autocomplete-props <path>
The following deprecated props have been removed from the Autocomplete component:
ChipProps→ useslotProps.chipcomponentsProps→ useslotPropsListboxComponent→ useslots.listboxListboxProps→ useslotProps.listboxPaperComponent→ useslots.paperPopperComponent→ useslots.popperrenderTags→ userenderValue
<Autocomplete
multiple
options={options}
renderInput={(params) => <TextField {...params} />}
- ChipProps={{ size: 'small' }}
- componentsProps={{
- clearIndicator: { size: 'large' },
- paper: { elevation: 2 },
- popper: { placement: 'bottom-end' },
- popupIndicator: { size: 'large' },
- }}
- ListboxComponent={CustomListbox}
- ListboxProps={{ style: { maxHeight: 200 }, ref }}
- PaperComponent={CustomPaper}
- PopperComponent={(props) => {
- const { disablePortal, anchorEl, open, ...other } = props;
- return <Box {...other} />;
- }}
- renderTags={(value, getTagProps, ownerState) =>
- value.map((option, index) => (
- <Chip label={option.label} {...getTagProps({ index })} />
- ))
- }
+ slots={{
+ listbox: CustomListbox,
+ paper: CustomPaper,
+ popper: (props) => {
+ const { disablePortal, anchorEl, open, ...other } = props;
+ return <Box {...other} />;
+ },
+ }}
+ slotProps={{
+ chip: { size: 'small' },
+ clearIndicator: { size: 'large' },
+ listbox: { style: { maxHeight: 200 }, ref },
+ paper: { elevation: 2 },
+ popper: { placement: 'bottom-end' },
+ popupIndicator: { size: 'large' },
+ }}
+ renderValue={(value, getItemProps, ownerState) =>
+ value.map((option, index) => (
+ <Chip label={option.label} {...getItemProps({ index })} />
+ ))
+ }
/>
useAutocomplete deprecated fields removed
Use the autocomplete-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/autocomplete-props <path>
The following deprecated members have been removed from the useAutocomplete hook return value:
getTagProps→ usegetItemPropsfocusedTag→ usefocusedItem
getTagProps
const {
- getTagProps,
+ getItemProps,
} = useAutocomplete(props);
// ...
-<Chip {...getTagProps({ index })} />
+<Chip {...getItemProps({ index })} />
focusedTag
const {
- focusedTag,
+ focusedItem,
} = useAutocomplete(props);
TextField deprecated props removed
Use the text-field-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/text-field-props <path>
The following deprecated props have been removed from the TextField component:
InputProps→ useslotProps.inputinputProps→ useslotProps.htmlInputSelectProps→ useslotProps.selectInputLabelProps→ useslotProps.inputLabelFormHelperTextProps→ useslotProps.formHelperText
<TextField
- InputProps={CustomInputProps}
- inputProps={CustomHtmlInputProps}
- SelectProps={CustomSelectProps}
- InputLabelProps={CustomInputLabelProps}
- FormHelperTextProps={CustomFormHelperTextProps}
+ slotProps={{
+ input: CustomInputProps,
+ htmlInput: CustomHtmlInputProps,
+ select: CustomSelectProps,
+ inputLabel: CustomInputLabelProps,
+ formHelperText: CustomFormHelperTextProps,
+ }}
/>
Use the autocomplete-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/autocomplete-props <path>
If you render a TextField from Autocomplete, the params shape also changed to match the new TextField API:
<Autocomplete
renderInput={(params) => (
<TextField
{...params}
- inputProps={{
- ...params.inputProps,
- autoComplete: 'new-password',
+ slotProps={{
+ ...params.slotProps,
+ htmlInput: {
+ ...params.slotProps.htmlInput,
+ autoComplete: 'new-password',
+ },
}}
/>
)}
Tooltip deprecated props removed
Use the tooltip-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/tooltip-props <path>
The following deprecated props have been removed from the Tooltip component:
components→ useslotscomponentsProps→ useslotPropsPopperComponent→ useslots.popperPopperProps→ useslotProps.popperTransitionComponent→ useslots.transitionTransitionProps→ useslotProps.transition
<Tooltip
title="Hello World"
- components={{ Popper: CustomPopper, Tooltip: CustomTooltip, Transition: CustomTransition, Arrow: CustomArrow }}
- componentsProps={{ popper: { placement: 'top' }, tooltip: { className: 'custom' }, arrow: { className: 'arrow' } }}
- PopperComponent={CustomPopper}
- PopperProps={{ disablePortal: true }}
- TransitionComponent={CustomTransition}
- TransitionProps={{ timeout: 500 }}
+ slots={{ popper: CustomPopper, tooltip: CustomTooltip, transition: CustomTransition, arrow: CustomArrow }}
+ slotProps={{
+ popper: { placement: 'top', disablePortal: true },
+ tooltip: { className: 'custom' },
+ transition: { timeout: 500 },
+ arrow: { className: 'arrow' },
+ }}
/>
Alert deprecated props removed
Use the alert-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/alert-props <path>
The following deprecated props have been removed from the Alert component:
components→ useslotscomponentsProps→ useslotProps
<Alert
onClose={handleClose}
- components={{ CloseIcon: MyCloseIcon, CloseButton: MyCloseButton }}
- componentsProps={{ closeButton: { size: 'large' }, closeIcon: { fontSize: 'small' } }}
+ slots={{ closeIcon: MyCloseIcon, closeButton: MyCloseButton }}
+ slotProps={{ closeButton: { size: 'large' }, closeIcon: { fontSize: 'small' } }}
/>
Accordion deprecated props removed
Use the accordion-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/accordion-props <path>
The following deprecated props have been removed from the Accordion component:
TransitionComponent→ useslots.transitionTransitionProps→ useslotProps.transition
<Accordion
- TransitionComponent={CustomTransition}
- TransitionProps={{ unmountOnExit: true }}
+ slots={{ transition: CustomTransition }}
+ slotProps={{ transition: { unmountOnExit: true } }}
>
AccordionSummary deprecated CSS classes removed
Use the accordion-summary-classes codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/accordion-summary-classes <path>
The deprecated AccordionSummary CSS class contentGutters has been removed.
Use the combination of .MuiAccordionSummary-gutters and .MuiAccordionSummary-content classes instead:
-.MuiAccordionSummary-contentGutters {
+.MuiAccordionSummary-gutters .MuiAccordionSummary-content {
margin: 20px 0;
}
Avatar deprecated props removed
Use the avatar-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/avatar-props <path>
The following deprecated props have been removed from the Avatar component:
imgProps→ useslotProps.img
-<Avatar imgProps={{ crossOrigin: 'anonymous', referrerPolicy: 'no-referrer' }} />
+<Avatar slotProps={{ img: { crossOrigin: 'anonymous', referrerPolicy: 'no-referrer' } }} />
AvatarGroup deprecated props removed
Use the avatar-group-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/avatar-group-props <path>
The following deprecated props have been removed from the AvatarGroup component:
componentsProps→ useslotProps(theadditionalAvatarkey has been renamed tosurplus)
-<AvatarGroup componentsProps={{ additionalAvatar: { className: 'my-class' } }}>
+<AvatarGroup slotProps={{ surplus: { className: 'my-class' } }}>
If you were already using the surplus key via componentsProps, move it to slotProps:
-<AvatarGroup componentsProps={{ surplus: { className: 'my-class' } }}>
+<AvatarGroup slotProps={{ surplus: { className: 'my-class' } }}>
Backdrop deprecated props removed
Use the backdrop-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/backdrop-props <path>
The following deprecated Backdrop props have been removed:
components— useslotsinsteadcomponentsProps— useslotPropsinsteadTransitionComponent— useslots.transitioninstead
<Backdrop
- components={{ Root: CustomRoot }}
- componentsProps={{ root: { className: 'my-class' } }}
- TransitionComponent={CustomTransition}
+ slots={{ root: CustomRoot, transition: CustomTransition }}
+ slotProps={{ root: { className: 'my-class' } }}
Badge deprecated props removed
Use the badge-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/badge-props <path>
The following deprecated props have been removed from the Badge component:
components→ useslotscomponentsProps→ useslotProps
<Badge
- components={{ Root: CustomRoot, Badge: CustomBadge }}
- componentsProps={{ root: { className: 'my-root' }, badge: { className: 'my-badge' } }}
+ slots={{ root: CustomRoot, badge: CustomBadge }}
+ slotProps={{ root: { className: 'my-root' }, badge: { className: 'my-badge' } }}
/>
Divider deprecated props removed
Use the codemod below to migrate the code as described in the following sections:
npx @mui/codemod@latest deprecations/divider-props <path>
The deprecated Divider prop have been removed.
Use sx={{ opacity : "0.6" }} (or any opacity):
<Divider
- light
+ sx={{ opacity: 0.6 }}
/>
Popper deprecated props removed
Use the popper-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/popper-props <path>
The following deprecated props have been removed:
components— useslotsinsteadcomponentsProps— useslotPropsinstead
<Popper
- components={{ Root: CustomRoot }}
- componentsProps={{ root: { className: 'custom' } }}
+ slots={{ root: CustomRoot }}
+ slotProps={{ root: { className: 'custom' } }}
/>
Slider deprecated props removed
Use the slider-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/slider-props <path>
The following deprecated props have been removed from the Slider component:
components— useslotsinsteadcomponentsProps— useslotPropsinstead
<Slider
- components={{ Track: CustomTrack }}
- componentsProps={{ track: { testid: 'test-id' } }}
+ slots={{ track: CustomTrack }}
+ slotProps={{ track: { testid: 'test-id' } }}
/>
Snackbar deprecated props removed
Use the snackbar-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/snackbar-props <path>
The following deprecated Snackbar props have been removed:
ClickAwayListenerProps— useslotProps.clickAwayListenerinsteadContentProps— useslotProps.contentinsteadTransitionComponent— useslots.transitioninsteadTransitionProps— useslotProps.transitioninstead
<Snackbar
- ClickAwayListenerProps={CustomClickAwayListenerProps}
- ContentProps={CustomContentProps}
- TransitionComponent={CustomTransition}
- TransitionProps={CustomTransitionProps}
+ slots={{ transition: CustomTransition }}
+ slotProps={{
+ clickAwayListener: CustomClickAwayListenerProps,
+ content: CustomContentProps,
+ transition: CustomTransitionProps,
+ }}
/>
SpeedDial deprecated props removed
The deprecated SpeedDial props have been removed.
Use the slots and slotProps props instead:
<SpeedDial
- TransitionComponent={CustomTransition}
- TransitionProps={{ timeout: 500 }}
+ slots={{ transition: CustomTransition }}
+ slotProps={{ transition: { timeout: 500 } }}
>
SpeedDialAction deprecated props removed
The deprecated SpeedDialAction props have been removed.
Use the slotProps prop instead:
<SpeedDialAction
- FabProps={{ size: 'large' }}
- tooltipTitle="Add"
- tooltipPlacement="right"
- tooltipOpen
- TooltipClasses={{ tooltip: 'custom' }}
+ slotProps={{
+ fab: { size: 'large' },
+ tooltip: {
+ title: 'Add',
+ placement: 'right',
+ open: true,
+ classes: { tooltip: 'custom' },
+ },
+ }}
/>
Tabs deprecated props removed
Use the tabs-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/tabs-props <path>
The following deprecated props have been removed:
ScrollButtonComponent— useslots.scrollButtonsinsteadTabIndicatorProps— useslotProps.indicatorinsteadTabScrollButtonProps— useslotProps.scrollButtonsinsteadslots.StartScrollButtonIcon— useslots.startScrollButtonIconinsteadslots.EndScrollButtonIcon— useslots.endScrollButtonIconinstead
<Tabs
- ScrollButtonComponent={CustomScrollButton}
- TabIndicatorProps={{ style: { backgroundColor: 'green' } }}
- TabScrollButtonProps={{ disableRipple: true }}
+ slots={{ scrollButtons: CustomScrollButton }}
+ slotProps={{
+ indicator: { style: { backgroundColor: 'green' } },
+ scrollButtons: { disableRipple: true },
+ }}
/>
FormControlLabel deprecated props removed
Use the form-control-label-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/form-control-label-props <path>
The following deprecated prop has been removed:
componentsProps— useslotPropsinstead
<FormControlLabel
- componentsProps={{ typography: { fontWeight: 'bold' } }}
+ slotProps={{ typography: { fontWeight: 'bold' } }}
/>
Typography deprecated CSS classes removed
The deprecated paragraph CSS class has been removed.
Use CSS .MuiTypography-root:where(p) to apply custom styles for the paragraph element instead:
-.MuiTypography-paragraph {
- margin-bottom: 16px;
-}
+.MuiTypography-root:where(p) {
+ margin-bottom: 16px;
+}
Typography deprecated props removed
Use the typography-props codemod below to migrate the code as described in the following section:
npx @mui/codemod@latest deprecations/typography-props <path>
The following deprecated props have been removed from the Typography component:
paragraph→ use thesxprop to add a margin bottom instead
-<Typography paragraph />
+<Typography sx={{ marginBottom: '16px' }} />