Reference


Popper
A Popper enables the display of content overlaid on another element, serving as an alternative to react-popper.
Key characteristics of the Popper component include:
- 🕷 The Popper depends on the third-party library (Popper.js) for precise positioning.
- 💄 It provides a simpler API compared to react-popper, prioritizing ease of use.
- Its child element utilizes a MUI Base Portal on the document body to prevent rendering issues. This can be disabled using the
disablePortal
prop. - Unlike the Popover component, the scroll remains unblocked, and the popper’s placement adjusts dynamically to the available viewport space.
- Clicking outside does not hide the Popper. For this functionality, consider using the MUI Base Click-Away Listener, as shown in the menu documentation section.
- The
anchorEl
prop is used as the reference object to initialize a newPopper.js
instance.
Basic Popper
import * as React from 'react'; import Box from '@mui/material/Box'; import Popper from '@mui/material/Popper'; export default function SimplePopper() { const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const handleClick = (event: React.MouseEvent<HTMLElement>) => { setAnchorEl(anchorEl ? null : event.currentTarget); }; const open = Boolean(anchorEl); const id = open ? 'simple-popper' : undefined; return ( <div> <button aria-describedby={id} type="button" onClick={handleClick}> Toggle Popper </button> <Popper id={id} open={open} anchorEl={anchorEl}> <Box sx={{ border: 1, p: 1, bgcolor: 'background.paper' }}> The content of the Popper. </Box> </Popper> </div> ); }
Transitions
The popper’s open/close state can be animated using a render prop child combined with a transition component. This component must adhere to these requirements:
- Be a direct child of the popper.
- Invoke the
onEnter
callback when the enter transition begins. - Invoke the
onExited
callback upon completion of the exit transition, allowing the popper to unmount the child content when closed and fully transitioned.
The Popper includes built-in support for react-transition-group.
import * as React from 'react'; import Box from '@mui/material/Box'; import Popper from '@mui/material/Popper'; import Fade from '@mui/material/Fade'; export default function TransitionsPopper() { const [open, setOpen] = React.useState(false); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const handleClick = (event: React.MouseEvent<HTMLElement>) => { setAnchorEl(event.currentTarget); setOpen((previousOpen) => !previousOpen); }; const canBeOpen = open && Boolean(anchorEl); const id = canBeOpen ? 'transition-popper' : undefined; return ( <div> <button aria-describedby={id} type="button" onClick={handleClick}> Toggle Popper </button> <Popper id={id} open={open} anchorEl={anchorEl} transition> {({ TransitionProps }) => ( <Fade {...TransitionProps} timeout={350}> <Box sx={{ border: 1, p: 1, bgcolor: 'background.paper' }}> The content of the Popper. </Box> </Fade> )} </Popper> </div> ); }
For an alternative you can leverage react spring.
import * as React from 'react'; import Box from '@mui/material/Box'; import Popper from '@mui/material/Popper'; import { useSpring, animated } from '@react-spring/web'; interface FadeProps { children?: React.ReactElement<unknown>; in?: boolean; onEnter?: () => void; onExited?: () => void; } const Fade = React.forwardRef<HTMLDivElement, FadeProps>(function Fade(props, ref) { const { in: open, children, onEnter, onExited, ...other } = props; const style = useSpring({ from: { opacity: 0 }, to: { opacity: open ? 1 : 0 }, onStart: () => { if (open && onEnter) { onEnter(); } }, onRest: () => { if (!open && onExited) { onExited(); } }, }); return ( // @ts-expect-error https://github.com/pmndrs/react-spring/issues/2341 <animated.div ref={ref} style={style} {...other}> {children} </animated.div> ); }); export default function SpringPopper() { const [open, setOpen] = React.useState(false); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const handleClick = (event: React.MouseEvent<HTMLElement>) => { setAnchorEl(event.currentTarget); setOpen((previousOpen) => !previousOpen); }; const canBeOpen = open && Boolean(anchorEl); const id = canBeOpen ? 'spring-popper' : undefined; return ( <div> <button aria-describedby={id} type="button" onClick={handleClick}> Toggle Popper </button> <Popper id={id} open={open} anchorEl={anchorEl} transition> {({ TransitionProps }) => ( <Fade {...TransitionProps}> <Box sx={{ border: 1, p: 1, bgcolor: 'background.paper' }}> The content of the Popper. </Box> </Fade> )} </Popper> </div> ); }
Positioned popper
import * as React from 'react'; import Box from '@mui/material/Box'; import Popper, { PopperPlacementType } from '@mui/material/Popper'; import Typography from '@mui/material/Typography'; import Grid from '@mui/material/Grid'; import Button from '@mui/material/Button'; import Fade from '@mui/material/Fade'; import Paper from '@mui/material/Paper'; export default function PositionedPopper() { const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null); const [open, setOpen] = React.useState(false); const [placement, setPlacement] = React.useState<PopperPlacementType>(); const handleClick = (newPlacement: PopperPlacementType) => (event: React.MouseEvent<HTMLButtonElement>) => { setAnchorEl(event.currentTarget); setOpen((prev) => placement !== newPlacement || !prev); setPlacement(newPlacement); }; return ( <Box sx={{ width: 500 }}> <Popper // Note: The following zIndex style is for documentation purposes only and may not be needed in your application. sx={{ zIndex: 1200 }} open={open} anchorEl={anchorEl} placement={placement} transition > {({ TransitionProps }) => ( <Fade {...TransitionProps} timeout={350}> <Paper> <Typography sx={{ p: 2 }}>The content of the Popper.</Typography> </Paper> </Fade> )} </Popper> <Grid container sx={{ justifyContent: 'center' }}> <Grid> <Button onClick={handleClick('top-start')}>top-start</Button> <Button onClick={handleClick('top')}>top</Button> <Button onClick={handleClick('top-end')}>top-end</Button> </Grid> </Grid> <Grid container sx={{ justifyContent: 'center' }}> <Grid size={6}> <Button onClick={handleClick('left-start')}>left-start</Button> <br /> <Button onClick={handleClick('left')}>left</Button> <br /> <Button onClick={handleClick('left-end')}>left-end</Button> </Grid> <Grid container direction="column" sx={{ alignItems: 'flex-end' }} size={6}> <Grid> <Button onClick={handleClick('right-start')}>right-start</Button> </Grid> <Grid> <Button onClick={handleClick('right')}>right</Button> </Grid> <Grid> <Button onClick={handleClick('right-end')}>right-end</Button> </Grid> </Grid> </Grid> <Grid container sx={{ justifyContent: 'center' }}> <Grid> <Button onClick={handleClick('bottom-start')}>bottom-start</Button> <Button onClick={handleClick('bottom')}>bottom</Button> <Button onClick={handleClick('bottom-end')}>bottom-end</Button> </Grid> </Grid> </Box> ); }
Scroll playground
Virtual element
The
anchorEl
prop can reference a virtual DOM element. You must create an object conforming to the VirtualElement
structure.Select a portion of the text to display the popper:
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ipsum purus, bibendum sit amet vulputate eget, porta semper ligula. Donec bibendum vulputate erat, ac fringilla mi finibus nec. Donec ac dolor sed dolor porttitor blandit vel vel purus. Fusce vel malesuada ligula. Nam quis vehicula ante, eu finibus est. Proin ullamcorper fermentum orci, quis finibus massa. Nunc lobortis, massa ut rutrum ultrices, metus metus finibus ex, sit amet facilisis neque enim sed neque. Quisque accumsan metus vel maximus consequat. Suspendisse lacinia tellus a libero volutpat maximus.
import * as React from 'react'; import Popper, { PopperProps } from '@mui/material/Popper'; import Typography from '@mui/material/Typography'; import Fade from '@mui/material/Fade'; import Paper from '@mui/material/Paper'; export default function VirtualElementPopper() { const [open, setOpen] = React.useState(false); const [anchorEl, setAnchorEl] = React.useState<PopperProps['anchorEl']>(null); const previousAnchorElPosition = React.useRef<DOMRect>(undefined); React.useEffect(() => { if (anchorEl) { if (typeof anchorEl === 'object') { previousAnchorElPosition.current = anchorEl.getBoundingClientRect(); } else { previousAnchorElPosition.current = anchorEl().getBoundingClientRect(); } } }, [anchorEl]); const handleClose = () => { setOpen(false); }; const handleMouseUp = () => { const selection = window.getSelection(); // Clears when the selection length is 0 if (!selection || selection.anchorOffset === selection.focusOffset) { handleClose(); return; } const getBoundingClientRect = () => { if (selection.rangeCount === 0 && previousAnchorElPosition.current) { setOpen(false); return previousAnchorElPosition.current; } return selection.getRangeAt(0).getBoundingClientRect(); }; setOpen(true); setAnchorEl({ getBoundingClientRect }); }; const id = open ? 'virtual-element-popper' : undefined; return ( <div onMouseLeave={handleClose}> <Typography aria-describedby={id} onMouseUp={handleMouseUp}> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ipsum purus, bibendum sit amet vulputate eget, porta semper ligula. Donec bibendum vulputate erat, ac fringilla mi finibus nec. Donec ac dolor sed dolor porttitor blandit vel vel purus. Fusce vel malesuada ligula. Nam quis vehicula ante, eu finibus est. Proin ullamcorper fermentum orci, quis finibus massa. Nunc lobortis, massa ut rutrum ultrices, metus metus finibus ex, sit amet facilisis neque enim sed neque. Quisque accumsan metus vel maximus consequat. Suspendisse lacinia tellus a libero volutpat maximus. </Typography> <Popper id={id} open={open} anchorEl={anchorEl} transition placement="bottom-start" > {({ TransitionProps }) => ( <Fade {...TransitionProps} timeout={350}> <Paper> <Typography sx={{ p: 2 }}>The content of the Popper.</Typography> </Paper> </Fade> )} </Popper> </div> ); }
Supplementary projects
For advanced scenarios, you may benefit from the following resources:
material-ui-popup-state
The
material-ui-popup-state
package simplifies managing popper state in most use cases.import * as React from 'react'; import Typography from '@mui/material/Typography'; import Button from '@mui/material/Button'; import Popper from '@mui/material/Popper'; import PopupState, { bindToggle, bindPopper } from 'material-ui-popup-state'; import Fade from '@mui/material/Fade'; import Paper from '@mui/material/Paper'; export default function PopperPopupState() { return ( <PopupState variant="popper" popupId="demo-popup-popper"> {(popupState) => ( <div> <Button variant="contained" {...bindToggle(popupState)}> Toggle Popper </Button> <Popper {...bindPopper(popupState)} transition> {({ TransitionProps }) => ( <Fade {...TransitionProps} timeout={350}> <Paper> <Typography sx={{ p: 2 }}>The content of the Popper.</Typography> </Paper> </Fade> )} </Popper> </div> )} </PopupState> ); }