Reference
Menu
Menus display a list of choices on temporary surfaces.
A menu displays a list of choices on a temporary surface. It appears when the user interacts with a button, or other control.
Basic menu
A basic menu opens over the anchor element by default (this option can be changed via props). When close to a screen edge, a basic menu vertically realigns to make sure that all menu items are completely visible.
Choosing an option should immediately ideally commit the option and close the menu.
Disambiguation: In contrast to simple menus, simple dialogs can present additional detail related to the options available for a list item or provide navigational or orthogonal actions related to the primary task. Although they can display the same content, simple menus are preferred over simple dialogs because simple menus are less disruptive to the user's current context.
import * as React from 'react'; import Button from '@mui/material/Button'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; export default function BasicMenu() { const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const open = Boolean(anchorEl); const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; return ( <div> <Button id="basic-button" aria-controls={open ? 'basic-menu' : undefined} aria-haspopup="true" aria-expanded={open ? 'true' : undefined} onClick={handleClick} > Dashboard </Button> <Menu id="basic-menu" anchorEl={anchorEl} open={open} onClose={handleClose} MenuListProps={{ 'aria-labelledby': 'basic-button', }} > <MenuItem onClick={handleClose}>Profile</MenuItem> <MenuItem onClick={handleClose}>My account</MenuItem> <MenuItem onClick={handleClose}>Logout</MenuItem> </Menu> </div> ); }
Icon menu
In desktop viewport, padding is increased to give more space to the menu.
- Cut
⌘X
- Copy
⌘C
- Paste
⌘V
- Web Clipboard
import * as React from 'react'; import Divider from '@mui/material/Divider'; import Paper from '@mui/material/Paper'; import MenuList from '@mui/material/MenuList'; import MenuItem from '@mui/material/MenuItem'; import ListItemText from '@mui/material/ListItemText'; import ListItemIcon from '@mui/material/ListItemIcon'; import Typography from '@mui/material/Typography'; import ContentCut from '@mui/icons-material/ContentCut'; import ContentCopy from '@mui/icons-material/ContentCopy'; import ContentPaste from '@mui/icons-material/ContentPaste'; import Cloud from '@mui/icons-material/Cloud'; export default function IconMenu() { return ( <Paper sx={{ width: 320, maxWidth: '100%' }}> <MenuList> <MenuItem> <ListItemIcon> <ContentCut fontSize="small" /> </ListItemIcon> <ListItemText>Cut</ListItemText> <Typography variant="body2" sx={{ color: 'text.secondary' }}> ⌘X </Typography> </MenuItem> <MenuItem> <ListItemIcon> <ContentCopy fontSize="small" /> </ListItemIcon> <ListItemText>Copy</ListItemText> <Typography variant="body2" sx={{ color: 'text.secondary' }}> ⌘C </Typography> </MenuItem> <MenuItem> <ListItemIcon> <ContentPaste fontSize="small" /> </ListItemIcon> <ListItemText>Paste</ListItemText> <Typography variant="body2" sx={{ color: 'text.secondary' }}> ⌘V </Typography> </MenuItem> <Divider /> <MenuItem> <ListItemIcon> <Cloud fontSize="small" /> </ListItemIcon> <ListItemText>Web Clipboard</ListItemText> </MenuItem> </MenuList> </Paper> ); }
Dense menu
For the menu that has long list and long text, you can use the
dense
prop to reduce the padding and text size.- Single
- 1.15
- Double
- Custom: 1.2
- Add space before paragraph
- Add space after paragraph
- Custom spacing...
import * as React from 'react'; import Paper from '@mui/material/Paper'; import Divider from '@mui/material/Divider'; import MenuList from '@mui/material/MenuList'; import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import Check from '@mui/icons-material/Check'; export default function DenseMenu() { return ( <Paper sx={{ width: 320 }}> <MenuList dense> <MenuItem> <ListItemText inset>Single</ListItemText> </MenuItem> <MenuItem> <ListItemText inset>1.15</ListItemText> </MenuItem> <MenuItem> <ListItemText inset>Double</ListItemText> </MenuItem> <MenuItem> <ListItemIcon> <Check /> </ListItemIcon> Custom: 1.2 </MenuItem> <Divider /> <MenuItem> <ListItemText>Add space before paragraph</ListItemText> </MenuItem> <MenuItem> <ListItemText>Add space after paragraph</ListItemText> </MenuItem> <Divider /> <MenuItem> <ListItemText>Custom spacing...</ListItemText> </MenuItem> </MenuList> </Paper> ); }
Selected menu
If used for item selection, when opened, simple menus places the initial focus on the selected menu item. The currently selected menu item is set using the
selected
prop (from ListItem). To use a selected menu item without impacting the initial focus, set the variant
prop to "menu".import * as React from 'react'; import List from '@mui/material/List'; import ListItemButton from '@mui/material/ListItemButton'; import ListItemText from '@mui/material/ListItemText'; import MenuItem from '@mui/material/MenuItem'; import Menu from '@mui/material/Menu'; const options = [ 'Show some love to MUI', 'Show all notification content', 'Hide sensitive notification content', 'Hide all notification content', ]; export default function SimpleListMenu() { const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const [selectedIndex, setSelectedIndex] = React.useState(1); const open = Boolean(anchorEl); const handleClickListItem = (event: React.MouseEvent<HTMLElement>) => { setAnchorEl(event.currentTarget); }; const handleMenuItemClick = ( event: React.MouseEvent<HTMLElement>, index: number, ) => { setSelectedIndex(index); setAnchorEl(null); }; const handleClose = () => { setAnchorEl(null); }; return ( <div> <List component="nav" aria-label="Device settings" sx={{ bgcolor: 'background.paper' }} > <ListItemButton id="lock-button" aria-haspopup="listbox" aria-controls="lock-menu" aria-label="when device is locked" aria-expanded={open ? 'true' : undefined} onClick={handleClickListItem} > <ListItemText primary="When device is locked" secondary={options[selectedIndex]} /> </ListItemButton> </List> <Menu id="lock-menu" anchorEl={anchorEl} open={open} onClose={handleClose} MenuListProps={{ 'aria-labelledby': 'lock-button', role: 'listbox', }} > {options.map((option, index) => ( <MenuItem key={option} disabled={index === 0} selected={index === selectedIndex} onClick={(event) => handleMenuItemClick(event, index)} > {option} </MenuItem> ))} </Menu> </div> ); }
Positioned menu
Because the
Menu
component uses the Popover
component to position itself, you can use the same positioning props to position it. For instance, you can display the menu on top of the anchor:import * as React from 'react'; import Button from '@mui/material/Button'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; export default function PositionedMenu() { const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const open = Boolean(anchorEl); const handleClick = (event: React.MouseEvent<HTMLElement>) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; return ( <div> <Button id="demo-positioned-button" aria-controls={open ? 'demo-positioned-menu' : undefined} aria-haspopup="true" aria-expanded={open ? 'true' : undefined} onClick={handleClick} > Dashboard </Button> <Menu id="demo-positioned-menu" aria-labelledby="demo-positioned-button" anchorEl={anchorEl} open={open} onClose={handleClose} anchorOrigin={{ vertical: 'top', horizontal: 'left', }} transformOrigin={{ vertical: 'top', horizontal: 'left', }} > <MenuItem onClick={handleClose}>Profile</MenuItem> <MenuItem onClick={handleClose}>My account</MenuItem> <MenuItem onClick={handleClose}>Logout</MenuItem> </Menu> </div> ); }
MenuList composition
The
Menu
component uses the Popover
component internally. However, you might want to use a different positioning strategy, or not blocking the scroll. For answering those needs, we expose a MenuList
component that you can compose, with Popper
in this example.The primary responsibility of the
MenuList
component is to handle the focus.- Profile
- My account
- Logout
import * as React from 'react'; import Button from '@mui/material/Button'; import ClickAwayListener from '@mui/material/ClickAwayListener'; import Grow from '@mui/material/Grow'; import Paper from '@mui/material/Paper'; import Popper from '@mui/material/Popper'; import MenuItem from '@mui/material/MenuItem'; import MenuList from '@mui/material/MenuList'; import Stack from '@mui/material/Stack'; export default function MenuListComposition() { const [open, setOpen] = React.useState(false); const anchorRef = React.useRef<HTMLButtonElement>(null); const handleToggle = () => { setOpen((prevOpen) => !prevOpen); }; const handleClose = (event: Event | React.SyntheticEvent) => { if ( anchorRef.current && anchorRef.current.contains(event.target as HTMLElement) ) { return; } setOpen(false); }; function handleListKeyDown(event: React.KeyboardEvent) { if (event.key === 'Tab') { event.preventDefault(); setOpen(false); } else if (event.key === 'Escape') { setOpen(false); } } // return focus to the button when we transitioned from !open -> open const prevOpen = React.useRef(open); React.useEffect(() => { if (prevOpen.current === true && open === false) { anchorRef.current!.focus(); } prevOpen.current = open; }, [open]); return ( <Stack direction="row" spacing={2}> <Paper> <MenuList> <MenuItem>Profile</MenuItem> <MenuItem>My account</MenuItem> <MenuItem>Logout</MenuItem> </MenuList> </Paper> <div> <Button ref={anchorRef} id="composition-button" aria-controls={open ? 'composition-menu' : undefined} aria-expanded={open ? 'true' : undefined} aria-haspopup="true" onClick={handleToggle} > Dashboard </Button> <Popper open={open} anchorEl={anchorRef.current} role={undefined} placement="bottom-start" transition disablePortal > {({ TransitionProps, placement }) => ( <Grow {...TransitionProps} style={{ transformOrigin: placement === 'bottom-start' ? 'left top' : 'left bottom', }} > <Paper> <ClickAwayListener onClickAway={handleClose}> <MenuList autoFocusItem={open} id="composition-menu" aria-labelledby="composition-button" onKeyDown={handleListKeyDown} > <MenuItem onClick={handleClose}>Profile</MenuItem> <MenuItem onClick={handleClose}>My account</MenuItem> <MenuItem onClick={handleClose}>Logout</MenuItem> </MenuList> </ClickAwayListener> </Paper> </Grow> )} </Popper> </div> </Stack> ); }
Account menu
Menu
content can be mixed with other components like Avatar
.Contact
Profile
import * as React from 'react'; import Box from '@mui/material/Box'; import Avatar from '@mui/material/Avatar'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import Divider from '@mui/material/Divider'; import IconButton from '@mui/material/IconButton'; import Typography from '@mui/material/Typography'; import Tooltip from '@mui/material/Tooltip'; import PersonAdd from '@mui/icons-material/PersonAdd'; import Settings from '@mui/icons-material/Settings'; import Logout from '@mui/icons-material/Logout'; export default function AccountMenu() { const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const open = Boolean(anchorEl); const handleClick = (event: React.MouseEvent<HTMLElement>) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; return ( <React.Fragment> <Box sx={{ display: 'flex', alignItems: 'center', textAlign: 'center' }}> <Typography sx={{ minWidth: 100 }}>Contact</Typography> <Typography sx={{ minWidth: 100 }}>Profile</Typography> <Tooltip title="Account settings"> <IconButton onClick={handleClick} size="small" sx={{ ml: 2 }} aria-controls={open ? 'account-menu' : undefined} aria-haspopup="true" aria-expanded={open ? 'true' : undefined} > <Avatar sx={{ width: 32, height: 32 }}>M</Avatar> </IconButton> </Tooltip> </Box> <Menu anchorEl={anchorEl} id="account-menu" open={open} onClose={handleClose} onClick={handleClose} slotProps={{ paper: { elevation: 0, sx: { overflow: 'visible', filter: 'drop-shadow(0px 2px 8px rgba(0,0,0,0.32))', mt: 1.5, '& .MuiAvatar-root': { width: 32, height: 32, ml: -0.5, mr: 1, }, '&::before': { content: '""', display: 'block', position: 'absolute', top: 0, right: 14, width: 10, height: 10, bgcolor: 'background.paper', transform: 'translateY(-50%) rotate(45deg)', zIndex: 0, }, }, }, }} transformOrigin={{ horizontal: 'right', vertical: 'top' }} anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} > <MenuItem onClick={handleClose}> <Avatar /> Profile </MenuItem> <MenuItem onClick={handleClose}> <Avatar /> My account </MenuItem> <Divider /> <MenuItem onClick={handleClose}> <ListItemIcon> <PersonAdd fontSize="small" /> </ListItemIcon> Add another account </MenuItem> <MenuItem onClick={handleClose}> <ListItemIcon> <Settings fontSize="small" /> </ListItemIcon> Settings </MenuItem> <MenuItem onClick={handleClose}> <ListItemIcon> <Logout fontSize="small" /> </ListItemIcon> Logout </MenuItem> </Menu> </React.Fragment> ); }
Customization
Here is an example of customizing the component. You can learn more about this in the overrides documentation page.
import * as React from 'react'; import { styled, alpha } from '@mui/material/styles'; import Button from '@mui/material/Button'; import Menu, { MenuProps } from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import EditIcon from '@mui/icons-material/Edit'; import Divider from '@mui/material/Divider'; import ArchiveIcon from '@mui/icons-material/Archive'; import FileCopyIcon from '@mui/icons-material/FileCopy'; import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; const StyledMenu = styled((props: MenuProps) => ( <Menu elevation={0} anchorOrigin={{ vertical: 'bottom', horizontal: 'right', }} transformOrigin={{ vertical: 'top', horizontal: 'right', }} {...props} /> ))(({ theme }) => ({ '& .MuiPaper-root': { borderRadius: 6, marginTop: theme.spacing(1), minWidth: 180, color: 'rgb(55, 65, 81)', boxShadow: 'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px', '& .MuiMenu-list': { padding: '4px 0', }, '& .MuiMenuItem-root': { '& .MuiSvgIcon-root': { fontSize: 18, color: theme.palette.text.secondary, marginRight: theme.spacing(1.5), }, '&:active': { backgroundColor: alpha( theme.palette.primary.main, theme.palette.action.selectedOpacity, ), }, }, ...theme.applyStyles('dark', { color: theme.palette.grey[300], }), }, })); export default function CustomizedMenus() { const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const open = Boolean(anchorEl); const handleClick = (event: React.MouseEvent<HTMLElement>) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; return ( <div> <Button id="demo-customized-button" aria-controls={open ? 'demo-customized-menu' : undefined} aria-haspopup="true" aria-expanded={open ? 'true' : undefined} variant="contained" disableElevation onClick={handleClick} endIcon={<KeyboardArrowDownIcon />} > Options </Button> <StyledMenu id="demo-customized-menu" MenuListProps={{ 'aria-labelledby': 'demo-customized-button', }} anchorEl={anchorEl} open={open} onClose={handleClose} > <MenuItem onClick={handleClose} disableRipple> <EditIcon /> Edit </MenuItem> <MenuItem onClick={handleClose} disableRipple> <FileCopyIcon /> Duplicate </MenuItem> <Divider sx={{ my: 0.5 }} /> <MenuItem onClick={handleClose} disableRipple> <ArchiveIcon /> Archive </MenuItem> <MenuItem onClick={handleClose} disableRipple> <MoreHorizIcon /> More </MenuItem> </StyledMenu> </div> ); }
The
MenuItem
is a wrapper around ListItem
with some additional styles. You can use the same list composition features with the MenuItem
component:🎨 If you are looking for inspiration, you can check MUI Treasury's customization examples.
Max height menu
If the height of a menu prevents all menu items from being displayed, the menu can scroll internally.
import * as React from 'react'; import IconButton from '@mui/material/IconButton'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import MoreVertIcon from '@mui/icons-material/MoreVert'; const options = [ 'None', 'Atria', 'Callisto', 'Dione', 'Ganymede', 'Hangouts Call', 'Luna', 'Oberon', 'Phobos', 'Pyxis', 'Sedna', 'Titania', 'Triton', 'Umbriel', ]; const ITEM_HEIGHT = 48; export default function LongMenu() { const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const open = Boolean(anchorEl); const handleClick = (event: React.MouseEvent<HTMLElement>) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; return ( <div> <IconButton aria-label="more" id="long-button" aria-controls={open ? 'long-menu' : undefined} aria-expanded={open ? 'true' : undefined} aria-haspopup="true" onClick={handleClick} > <MoreVertIcon /> </IconButton> <Menu id="long-menu" MenuListProps={{ 'aria-labelledby': 'long-button', }} anchorEl={anchorEl} open={open} onClose={handleClose} slotProps={{ paper: { style: { maxHeight: ITEM_HEIGHT * 4.5, width: '20ch', }, }, }} > {options.map((option) => ( <MenuItem key={option} selected={option === 'Pyxis'} onClick={handleClose}> {option} </MenuItem> ))} </Menu> </div> ); }
Limitations
There is a flexbox bug that prevents
text-overflow: ellipsis
from working in a flexbox layout. You can use the Typography
component with noWrap
to workaround this issue:A short message
A very long text that overflows
A very long text that overflows
import * as React from 'react'; import MenuList from '@mui/material/MenuList'; import MenuItem from '@mui/material/MenuItem'; import Paper from '@mui/material/Paper'; import ListItemIcon from '@mui/material/ListItemIcon'; import Typography from '@mui/material/Typography'; import DraftsIcon from '@mui/icons-material/Drafts'; import SendIcon from '@mui/icons-material/Send'; import PriorityHighIcon from '@mui/icons-material/PriorityHigh'; export default function TypographyMenu() { return ( <Paper sx={{ width: 230 }}> <MenuList> <MenuItem> <ListItemIcon> <SendIcon fontSize="small" /> </ListItemIcon> <Typography variant="inherit">A short message</Typography> </MenuItem> <MenuItem> <ListItemIcon> <PriorityHighIcon fontSize="small" /> </ListItemIcon> <Typography variant="inherit">A very long text that overflows</Typography> </MenuItem> <MenuItem> <ListItemIcon> <DraftsIcon fontSize="small" /> </ListItemIcon> <Typography variant="inherit" noWrap> A very long text that overflows </Typography> </MenuItem> </MenuList> </Paper> ); }
Change transition
Use a different transition.
import * as React from 'react'; import Button from '@mui/material/Button'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import Fade from '@mui/material/Fade'; export default function FadeMenu() { const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const open = Boolean(anchorEl); const handleClick = (event: React.MouseEvent<HTMLElement>) => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; return ( <div> <Button id="fade-button" aria-controls={open ? 'fade-menu' : undefined} aria-haspopup="true" aria-expanded={open ? 'true' : undefined} onClick={handleClick} > Dashboard </Button> <Menu id="fade-menu" MenuListProps={{ 'aria-labelledby': 'fade-button', }} anchorEl={anchorEl} open={open} onClose={handleClose} TransitionComponent={Fade} > <MenuItem onClick={handleClose}>Profile</MenuItem> <MenuItem onClick={handleClose}>My account</MenuItem> <MenuItem onClick={handleClose}>Logout</MenuItem> </Menu> </div> ); }
Context menu
Here is an example of a context menu. (Right click to open.)
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 Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; import Typography from '@mui/material/Typography'; export default function ContextMenu() { const [contextMenu, setContextMenu] = React.useState<{ mouseX: number; mouseY: number; } | null>(null); const handleContextMenu = (event: React.MouseEvent) => { event.preventDefault(); setContextMenu( contextMenu === null ? { mouseX: event.clientX + 2, mouseY: event.clientY - 6, } : // repeated contextmenu when it is already open closes it with Chrome 84 on Ubuntu // Other native context menus might behave different. // With this behavior we prevent contextmenu from the backdrop to re-locale existing context menus. null, ); }; const handleClose = () => { setContextMenu(null); }; return ( <div onContextMenu={handleContextMenu} style={{ cursor: 'context-menu' }}> <Typography> 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> <Menu open={contextMenu !== null} onClose={handleClose} anchorReference="anchorPosition" anchorPosition={ contextMenu !== null ? { top: contextMenu.mouseY, left: contextMenu.mouseX } : undefined } > <MenuItem onClick={handleClose}>Copy</MenuItem> <MenuItem onClick={handleClose}>Print</MenuItem> <MenuItem onClick={handleClose}>Highlight</MenuItem> <MenuItem onClick={handleClose}>Email</MenuItem> </Menu> </div> ); }
Supplementary projects
For more advanced use cases you might be able to take advantage of:
material-ui-popup-state
The package
material-ui-popup-state
that takes care of menu state for you in most cases.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'; export default function MenuPopupState() { return ( <PopupState variant="popover" popupId="demo-popup-menu"> {(popupState) => ( <React.Fragment> <Button variant="contained" {...bindTrigger(popupState)}> Dashboard </Button> <Menu {...bindMenu(popupState)}> <MenuItem onClick={popupState.close}>Profile</MenuItem> <MenuItem onClick={popupState.close}>My account</MenuItem> <MenuItem onClick={popupState.close}>Logout</MenuItem> </Menu> </React.Fragment> )} </PopupState> ); }