easiest way to create menus, popovers, and poppers with material-ui
npm install material-ui-popup-state



Takes care of the boilerplate for common Menu, Popover and Popper use cases.
Provides a Custom React Hook that keeps track of the local state for a single popup, and functions to connect trigger, toggle, and
popover/menu/popper components to the state.
Also provides a Render Props Component that
keeps track of the local state for a single popup, and passes the state and
mutation functions to a child render function.
Requires MUI >= 5.0.0 and React >= 16.8.0.
For MUI v4 you'll need material-ui-popup-state@^1.9.3.
- material-ui-popup-state
- Requirements
- Table of Contents
- Installation
- Examples with React Hooks
- Menu
- Popover
- Popper
- React Hooks API
- Bind Functions
- usePopupState
- usePopupState Props
- variant ('popover', 'popper', or 'dialog', required)
- popupId (string, optional)
- disableAutoFocus (boolean, optional)
- usePopupState return value
- Examples with Render Props
- Menu
- Popover
- Mouse Over Interaction
- Popper
- Render Props API
- Bind Functions
- PopupState Props
- variant ('popover', 'popper', or 'dialog', required)
- popupId (string, optional)
- disableAutoFocus (boolean, optional)
- children ((popupState: InjectedProps) => ?React.Node, required)
- Using Popover and Menu with bindHover
- Chaining event handlers
- Chaining event handlers manually
- Using material-ui-popup-state/chainEventHandlers
``sh`
npm install --save material-ui-popup-state
`js
import * as React from 'react'
import Button from '@mui/material/Button'
import Menu from '@mui/material/Menu'
import MenuItem from '@mui/material/MenuItem'
import {
usePopupState,
bindTrigger,
bindMenu,
} from 'material-ui-popup-state/hooks'
const MenuPopupState = () => {
const popupState = usePopupState({ variant: 'popover', popupId: 'demoMenu' })
return (
export default MenuPopupState
`
`js
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@mui/material/styles'
import Typography from '@mui/material/Typography'
import Button from '@mui/material/Button'
import Popover from '@mui/material/Popover'
import {
usePopupState,
bindTrigger,
bindPopover,
} from 'material-ui-popup-state/hooks'
const styles = (theme) => ({
typography: {
margin: theme.spacing.unit * 2,
},
})
const PopoverPopupState = ({ classes }) => {
const popupState = usePopupState({
variant: 'popover',
popupId: 'demoPopover',
})
return (
PopoverPopupState.propTypes = {
classes: PropTypes.object.isRequired,
}
export default withStyles(styles)(PopoverPopupState)
`
`js
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@mui/material/styles'
import Typography from '@mui/material/Typography'
import Button from '@mui/material/Button'
import Popper from '@mui/material/Popper'
import {
usePopupState,
bindToggle,
bindPopper,
} from 'material-ui-popup-state/hooks'
import Fade from '@mui/material/Fade'
import Paper from '@mui/material/Paper'
const styles = (theme) => ({
typography: {
padding: theme.spacing.unit * 2,
},
})
const PopperPopupState = ({ classes }) => {
const popupState = usePopupState({ variant: 'popper', popupId: 'demoPopper' })
return (
PopperPopupState.propTypes = {
classes: PropTypes.object.isRequired,
}
export default withStyles(styles)(PopperPopupState)
`
material-ui-popup-state/hooks exports several helper functions you can use to
connect components easily:
- anchorRef: creates a ref function to pass to the anchorElcurrentTarget
(by default, the of the mouse event that triggered the popupanchorRef
is used; only use if you want a different element to be the anchor).bindMenu
- : creates props to control a Menu component.bindPopover
- : creates props to control a Popover component.bindPopper
- : creates props to control a Popper component.bindDialog
- : creates props to control a Dialog component.bindTrigger
- : creates props for a component that opens the popup when clicked.bindContextMenu
- : creates props for a component that opens the popup on when right clicked (contextmenu event).bindPopover
NOTE: /bindMenu will position the Popover/Menu to the contextmenu event location. To positioncontextmenu
using the target element instead, pass anchorReference="anchorEl" after {...bindPopover(popupState)}/{...bindMenu(popupState)}.bindToggle
- : creates props for a component that toggles the popup when clicked.bindHover
- : creates props for a component that opens the popup while hovered.bindHover
NOTE: See this guidance if you are using with Popover or Menu.bindFocus
- : creates props for a component that opens the popup while focus.bindDoubleClick
- : creates props for a component that opens the popup while double click.
To use one of these functions, you should call it with the object
returned by usePopupState and spread the return value into the desired
element:
`js
import * as React from 'react'
import Button from '@mui/material/Button'
import Menu from '@mui/material/Menu'
import MenuItem from '@mui/material/MenuItem'
import {
usePopupState,
bindTrigger,
bindMenu,
} from 'material-ui-popup-state/hooks'
const MenuPopupState = () => {
const popupState = usePopupState({ variant: 'popover', popupId: 'demoMenu' })
return (
export default MenuPopupState
`
This is a Custom Hook that uses useState internally, therefore the Rules of Hooks apply to usePopupState.
PropsUse 'popover' if your popup is a Popover or Menu; use 'popper' if yourPopper
popup is a .
Right now this only affects whether bindTrigger/bindToggle/bindHover returnaria-controls
an prop or an aria-describedby prop.
The id for the popup component. It will be passed to the child props so that
the trigger component may declare the same id in an ARIA prop.
Defaults to React.useId() if React.useId exists; in older versions of ReactpopupId
you will have to manually provide a .
If true, will not steal focus when the popup is opened. (And bindPopover/bindMenu will inject disableAutoFocus, disableEnforceFocus, and disableRestoreFocus).
Defaults to true when the popup is opened by the bindHover or bindFocus element.
return valueAn object with the following properties:
- open([eventOrAnchorEl]): opens the popup. You must pass in an anchor element or an event with a currentTarget, otherwise the popup will not position properly and you will get a warning; MUI needs an anchor element to position the popup.close()
- : closes the popuptoggle([eventOrAnchorEl])
- : opens the popup if it is closed, or closes the popup if it is open. If the popup is currently closed, you must pass an anchor element or an event with a currentTarget, otherwise the popup will not position properly and you will get a warning; MUI needs an anchor element to position the popup.setOpen(open, [eventOrAnchorEl])
- : sets whether the popup is open. If open is truthy, you must pass in an anchor element or an event with a currentTarget, otherwise the popup will not position properly and you will get a warning; MUI needs an anchor element to position the popup.isOpen
- : true/false if the popup is open/closedanchorEl
- : the current anchor elementanchorPosition
- : the current anchor positionsetAnchorEl
- : sets the anchor element (the currentTarget of the triggeringsetAnchorEl
mouse event is used by default unless you have called )popupId
- : the popupId prop you passed to PopupStatevariant
- : the variant prop you passed to PopupState
`js
import * as React from 'react'
import Button from '@mui/material/Button'
import Menu from '@mui/material/Menu'
import MenuItem from '@mui/material/MenuItem'
import PopupState, { bindTrigger, bindMenu } from 'material-ui-popup-state'
const MenuPopupState = () => (
{(popupState) => (
)}
)
export default MenuPopupState
`
`js
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@mui/material/styles'
import Typography from '@mui/material/Typography'
import Button from '@mui/material/Button'
import Popover from '@mui/material/Popover'
import PopupState, { bindTrigger, bindPopover } from 'material-ui-popup-state'
const styles = (theme) => ({
typography: {
margin: theme.spacing.unit * 2,
},
})
const PopoverPopupState = ({ classes }) => (
{(popupState) => (
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
>
The content of the Popover.
)}
)
PopoverPopupState.propTypes = {
classes: PropTypes.object.isRequired,
}
export default withStyles(styles)(PopoverPopupState)
`
`js
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@mui/material/styles'
import Typography from '@mui/material/Typography'
import HoverPopover from 'material-ui-popup-state/HoverPopover'
import PopupState, { bindHover, bindPopover } from 'material-ui-popup-state'
const styles = (theme) => ({
popover: {
pointerEvents: 'none',
},
paper: {
padding: theme.spacing.unit,
},
})
const HoverPopoverPopupState = ({ classes }) => (
{(popupState) => (
Hover with a Popover.
className={classes.popover}
classes={{
paper: classes.paper,
}}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
>
)}
)
HoverPopoverPopupState.propTypes = {
classes: PropTypes.object.isRequired,
}
export default withStyles(styles)(HoverPopoverPopupState)
`
`js
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@mui/material/styles'
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'
const styles = (theme) => ({
typography: {
padding: theme.spacing.unit * 2,
},
})
const PopperPopupState = ({ classes }) => (
{(popupState) => (
{({ TransitionProps }) => (
The content of the Popper.
)}
)}
)
PopperPopupState.propTypes = {
classes: PropTypes.object.isRequired,
}
export default withStyles(styles)(PopperPopupState)
`
material-ui-popup-state exports several helper functions you can use to
connect components easily:
- anchorRef: creates a ref function to pass to the anchorElcurrentTarget
(by default, the of the mouse event that triggered the popupanchorRef
is used; only use if you want a different element to be the anchor).bindMenu
- : creates props to control a Menu component.bindPopover
- : creates props to control a Popover component.bindPopper
- : creates props to control a Popper component.bindDialog
- : creates props to control a Dialog component.bindTrigger
- : creates props for a component that opens the popup when clicked.bindContextMenu
- : creates props for a component that opens the popup on when right clicked (contextmenu event).bindPopover
NOTE: /bindMenu will position the Popover/Menu to the contextmenu event location. To positioncontextmenu
using the target element instead, pass anchorReference="anchorEl" after {...bindPopover(popupState)}/{...bindMenu(popupState)}.bindToggle
- : creates props for a component that toggles the popup when clicked.bindHover
- : creates props for a component that opens the popup while hovered.bindHover
NOTE: See this guidance if you are using with Popover or Menu.bindFocus
- : creates props for a component that opens the popup while hovered.
To use one of these functions, you should call it with the props PopupState
passed to your child function, and spread the return value into the desired
element:
`js
import * as React from 'react'
import Button from '@mui/material/Button'
import Menu from '@mui/material/Menu'
import MenuItem from '@mui/material/MenuItem'
import PopupState, { bindTrigger, bindMenu } from 'material-ui-popup-state'
const MenuPopupState = () => (
{(popupState) => (
)}
)
export default MenuPopupState
`
PropsUse 'popover' if your popup is a Popover or Menu; use 'popper' if yourPopper
popup is a .
Right now this only affects whether bindTrigger/bindToggle/bindHover returnaria-controls
an prop or an aria-describedby prop.
The id for the popup component. It will be passed to the child props so that
the trigger component may declare the same id in an ARIA prop.
Defaults to React.useId() if React.useId exists; in older versions of ReactpopupId
you will have to manually provide a .
If true, will not steal focus when the popup is opened. (And bindPopover/bindMenu will inject disableAutoFocus, disableEnforceFocus, and disableRestoreFocus).
Defaults to true when the popup is opened by the bindHover or bindFocus element.
The render function. It will be called with an object containing the following
props (exported as the InjectedProps type):
- open([eventOrAnchorEl]): opens the popupclose()
- : closes the popuptoggle([eventOrAnchorEl])
- : opens the popup if it is closed, or closes the popup if it is open.setOpen(open, [eventOrAnchorEl])
- : sets whether the popup is open.isOpen
- : true/false if the popup is open/closedanchorEl
- : the current anchor elementanchorPosition
- : the current anchor positionsetAnchorEl
- : sets the anchor element (the currentTarget of the triggeringsetAnchorEl
mouse event is used by default unless you have called )popupId
- : the popupId prop you passed to PopupStatevariant
- : the variant prop you passed to PopupState
and Menu with bindHoverMUI's Modal (used by Popover and Menu) blocks pointer events to all other components, interfering with bindHoverbindHover
(the popover or menu will open when the mouse enters the element, but won't close when the mouse leaves). You can
use the following components to work around this:
`js`
import HoverMenu from 'material-ui-popup-state/HoverMenu'
import HoverPopover from 'material-ui-popup-state/HoverPopover'
These are just wrapper components that pass inline styles to prevent Modal from blocking pointer events.
What if you need to perform additional actions in onClick, but it's being injected by {...bindTrigger(popupState)} etc?
There are two options:
This is the most straightforward, explicit option.
`tsx`
const button = (
{...bindTrigger(popupState)}
onClick={(e: React.MouseEvent) => {
bindTrigger(popupState).onClick(e)
performCustomAction(e)
}}
>
Open Menu
)
If you don't like the above option, you can use the provided material-ui-popup-state/chainEventHandlers helper:
`tsx
import { chainEventHandlers } from 'material-ui-popup-state/chainEventHandlers'
const button = (
{...chainEventHandlers(bindTrigger(popupState), {
onClick: (e: React.MouseEvent) => {
bindTrigger(popupState).onClick(e)
performCustomAction(e)
},
})}
>
Open Menu
)
`
chainEventHandlers accepts a variable number of props arguments and combines any function props of the same nameObject.assign
into a function that invokes the chained functions in sequence. For all other properties the behavior is like.
> [!WARNING]
> chainEventHandlers doesn't memoize the combined event handler functions, so they will cause components toReact.useCallback` and chaining event handlers manually.
> rerender. If you need memoized functions, you will need to perform the memoization with your own code, for example
> using