Reference
Transitions
Transitions help to make a UI expressive and easy to use.
Material UI provides transitions that can be used to introduce some basic motion to your applications.
Collapse
Expand from the start edge of the child element. Use the
orientation
prop if you need a horizontal collapse. The collapsedSize
prop can be used to set the minimum width/height when not expanded.import * as React from 'react'; import Box from '@mui/material/Box'; import Switch from '@mui/material/Switch'; import Paper from '@mui/material/Paper'; import Collapse from '@mui/material/Collapse'; import FormControlLabel from '@mui/material/FormControlLabel'; const icon = ( <Paper sx={{ m: 1, width: 100, height: 100 }} elevation={4}> <svg> <Box component="polygon" points="0,100 50,00, 100,100" sx={(theme) => ({ fill: theme.palette.common.white, stroke: theme.palette.divider, strokeWidth: 1, })} /> </svg> </Paper> ); export default function SimpleCollapse() { const [checked, setChecked] = React.useState(false); const handleChange = () => { setChecked((prev) => !prev); }; return ( <Box sx={{ height: 300 }}> <FormControlLabel control={<Switch checked={checked} onChange={handleChange} />} label="Show" /> <Box sx={{ '& > :not(style)': { display: 'flex', justifyContent: 'space-around', height: 120, width: 250, }, }} > <div> <Collapse in={checked}>{icon}</Collapse> <Collapse in={checked} collapsedSize={40}> {icon} </Collapse> </div> <div> <Box sx={{ width: '50%' }}> <Collapse orientation="horizontal" in={checked}> {icon} </Collapse> </Box> <Box sx={{ width: '50%' }}> <Collapse orientation="horizontal" in={checked} collapsedSize={40}> {icon} </Collapse> </Box> </div> </Box> </Box> ); }
Fade
Fade in from transparent to opaque.
import * as React from 'react'; import Box from '@mui/material/Box'; import Switch from '@mui/material/Switch'; import Paper from '@mui/material/Paper'; import Fade from '@mui/material/Fade'; import FormControlLabel from '@mui/material/FormControlLabel'; const icon = ( <Paper sx={{ m: 1, width: 100, height: 100 }} elevation={4}> <svg> <Box component="polygon" points="0,100 50,00, 100,100" sx={(theme) => ({ fill: theme.palette.common.white, stroke: theme.palette.divider, strokeWidth: 1, })} /> </svg> </Paper> ); export default function SimpleFade() { const [checked, setChecked] = React.useState(false); const handleChange = () => { setChecked((prev) => !prev); }; return ( <Box sx={{ height: 180 }}> <FormControlLabel control={<Switch checked={checked} onChange={handleChange} />} label="Show" /> <Box sx={{ display: 'flex' }}> <Fade in={checked}>{icon}</Fade> </Box> </Box> ); }
Grow
Expands outwards from the center of the child element, while also fading in from transparent to opaque.
The second example demonstrates how to change the
transform-origin
, and conditionally applies the timeout
prop to change the entry speed.import * as React from 'react'; import Box from '@mui/material/Box'; import Switch from '@mui/material/Switch'; import Paper from '@mui/material/Paper'; import Grow from '@mui/material/Grow'; import FormControlLabel from '@mui/material/FormControlLabel'; const icon = ( <Paper sx={{ m: 1, width: 100, height: 100 }} elevation={4}> <svg> <Box component="polygon" points="0,100 50,00, 100,100" sx={(theme) => ({ fill: theme.palette.common.white, stroke: theme.palette.divider, strokeWidth: 1, })} /> </svg> </Paper> ); export default function SimpleGrow() { const [checked, setChecked] = React.useState(false); const handleChange = () => { setChecked((prev) => !prev); }; return ( <Box sx={{ height: 180 }}> <FormControlLabel control={<Switch checked={checked} onChange={handleChange} />} label="Show" /> <Box sx={{ display: 'flex' }}> <Grow in={checked}>{icon}</Grow> {/* Conditionally applies the timeout prop to change the entry speed. */} <Grow in={checked} style={{ transformOrigin: '0 0 0' }} {...(checked ? { timeout: 1000 } : {})} > {icon} </Grow> </Box> </Box> ); }
Slide
Slide in from the edge of the screen. The
direction
prop controls which edge of the screen the transition starts from.The Transition component's
mountOnEnter
prop prevents the child component from being mounted until in
is true
. This prevents the relatively positioned component from scrolling into view from its off-screen position. Similarly, the unmountOnExit
prop removes the component from the DOM after it has been transition off-screen.import * as React from 'react'; import Box from '@mui/material/Box'; import Switch from '@mui/material/Switch'; import Paper from '@mui/material/Paper'; import Slide from '@mui/material/Slide'; import FormControlLabel from '@mui/material/FormControlLabel'; const icon = ( <Paper sx={{ m: 1, width: 100, height: 100 }} elevation={4}> <svg> <Box component="polygon" points="0,100 50,00, 100,100" sx={(theme) => ({ fill: theme.palette.common.white, stroke: theme.palette.divider, strokeWidth: 1, })} /> </svg> </Paper> ); export default function SimpleSlide() { const [checked, setChecked] = React.useState(false); const handleChange = () => { setChecked((prev) => !prev); }; return ( <Box sx={{ height: 180, width: 130, position: 'relative', zIndex: 1 }}> <FormControlLabel control={<Switch checked={checked} onChange={handleChange} />} label="Show" /> <Slide direction="up" in={checked} mountOnEnter unmountOnExit> {icon} </Slide> </Box> ); }
Slide relative to a container
The Slide component also accepts
container
prop, which is a reference to a DOM node. If this prop is set, the Slide component will slide from the edge of that DOM node.import * as React from 'react'; import Box from '@mui/material/Box'; import Switch from '@mui/material/Switch'; import Paper from '@mui/material/Paper'; import Slide from '@mui/material/Slide'; import FormControlLabel from '@mui/material/FormControlLabel'; const icon = ( <Paper sx={{ m: 1, width: 100, height: 100 }} elevation={4}> <svg> <Box component="polygon" points="0,100 50,00, 100,100" sx={(theme) => ({ fill: theme.palette.common.white, stroke: theme.palette.divider, strokeWidth: 1, })} /> </svg> </Paper> ); export default function SlideFromContainer() { const [checked, setChecked] = React.useState(false); const containerRef = React.useRef<HTMLElement>(null); const handleChange = () => { setChecked((prev) => !prev); }; return ( <Box sx={{ width: 240, borderRadius: 2, border: '1px solid', borderColor: 'divider', backgroundColor: 'background.default', }} > <Box sx={{ p: 2, height: 200, overflow: 'hidden' }} ref={containerRef}> <FormControlLabel control={<Switch checked={checked} onChange={handleChange} />} label="Show from target" /> <Slide in={checked} container={containerRef.current}> {icon} </Slide> </Box> </Box> ); }
Zoom
Expand outwards from the center of the child element.
This example also demonstrates how to delay the enter transition.
import * as React from 'react'; import Box from '@mui/material/Box'; import Switch from '@mui/material/Switch'; import Paper from '@mui/material/Paper'; import Zoom from '@mui/material/Zoom'; import FormControlLabel from '@mui/material/FormControlLabel'; const icon = ( <Paper sx={{ m: 1, width: 100, height: 100 }} elevation={4}> <svg> <Box component="polygon" points="0,100 50,00, 100,100" sx={(theme) => ({ fill: theme.palette.common.white, stroke: theme.palette.divider, strokeWidth: 1, })} /> </svg> </Paper> ); export default function SimpleZoom() { const [checked, setChecked] = React.useState(false); const handleChange = () => { setChecked((prev) => !prev); }; return ( <Box sx={{ height: 180 }}> <FormControlLabel control={<Switch checked={checked} onChange={handleChange} />} label="Show" /> <Box sx={{ display: 'flex' }}> <Zoom in={checked}>{icon}</Zoom> <Zoom in={checked} style={{ transitionDelay: checked ? '500ms' : '0ms' }}> {icon} </Zoom> </Box> </Box> ); }
Child requirement
- Forward the style: To better support server rendering, Material UI provides a
style
prop to the children of some transition components (Fade, Grow, Zoom, Slide). Thestyle
prop must be applied to the DOM for the animation to work as expected. - Forward the ref: The transition components require the first child element to forward its ref to the DOM node. For more details about ref, check out Caveat with refs
- Single element: The transition components require only one child element (
React.Fragment
is not allowed).
// The `props` object contains a `style` prop. // You need to provide it to the `div` element as shown here. const MyComponent = React.forwardRef(function (props, ref) { return ( <div ref={ref} {...props}> Fade </div> ); }); export default function Main() { return ( <Fade> {/* MyComponent must be the only child */} <MyComponent /> </Fade> ); }
TransitionGroup
To animate a component when it is mounted or unmounted, you can use the
TransitionGroup
component from react-transition-group. As components are added or removed, the in
prop is toggled automatically by TransitionGroup
.- 🍏 Apple
- 🍌 Banana
- 🍍 Pineapple
import * as React from 'react'; import Button from '@mui/material/Button'; import Collapse from '@mui/material/Collapse'; import IconButton from '@mui/material/IconButton'; import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import ListItemText from '@mui/material/ListItemText'; import DeleteIcon from '@mui/icons-material/Delete'; import { TransitionGroup } from 'react-transition-group'; const FRUITS = [ '🍏 Apple', '🍌 Banana', '🍍 Pineapple', '🥥 Coconut', '🍉 Watermelon', ]; interface RenderItemOptions { item: string; handleRemoveFruit: (item: string) => void; } function renderItem({ item, handleRemoveFruit }: RenderItemOptions) { return ( <ListItem secondaryAction={ <IconButton edge="end" aria-label="delete" title="Delete" onClick={() => handleRemoveFruit(item)} > <DeleteIcon /> </IconButton> } > <ListItemText primary={item} /> </ListItem> ); } export default function TransitionGroupExample() { const [fruitsInBasket, setFruitsInBasket] = React.useState(FRUITS.slice(0, 3)); const handleAddFruit = () => { const nextHiddenItem = FRUITS.find((i) => !fruitsInBasket.includes(i)); if (nextHiddenItem) { setFruitsInBasket((prev) => [nextHiddenItem, ...prev]); } }; const handleRemoveFruit = (item: string) => { setFruitsInBasket((prev) => [...prev.filter((i) => i !== item)]); }; const addFruitButton = ( <Button variant="contained" disabled={fruitsInBasket.length >= FRUITS.length} onClick={handleAddFruit} > Add fruit to basket </Button> ); return ( <div> {addFruitButton} <List sx={{ mt: 1 }}> <TransitionGroup> {fruitsInBasket.map((item) => ( <Collapse key={item}>{renderItem({ item, handleRemoveFruit })}</Collapse> ))} </TransitionGroup> </List> </div> ); }
TransitionComponent prop
Some Material UI components use these transitions internally. These accept a
TransitionComponent
prop to customize the default transition. You can use any of the above components or your own. It should respect the following conditions:- Accepts an
in
prop. This corresponds to the open/close state. - Call the
onEnter
callback prop when the enter transition starts. - Call the
onExited
callback prop when the exit transition is completed. These two callbacks allow to unmount the children when in a closed state and fully transitioned.
For more information on creating a custom transition, visit the react-transition-group
Transition
documentation. You can also visit the dedicated sections of some of the components:Performance & SEO
The content of transition component is mounted by default even if
in={false}
. This default behavior has server-side rendering and SEO in mind. If you render expensive component trees inside your transition it might be a good idea to change this default behavior by enabling theunmountOnExit
prop:<Fade in={false} unmountOnExit />
As with any performance optimization this is not a silver bullet. Be sure to identify bottlenecks first and then try out these optimization strategies.