Menu

Building a Dropdown User Menu with Avatars Using React MUI Menu

As a front-end developer working with React and Material UI, you'll often need to create user interface components that combine functionality with visual appeal. One common UI pattern is the dropdown user menu - a compact component that expands to reveal user-related actions when clicked. In this article, I'll walk you through building a professional dropdown user menu with avatars using MUI's Menu component.

What You'll Learn

By the end of this article, you'll know how to:

  • Implement a dropdown user menu with proper menu positioning
  • Integrate user avatars into menu items
  • Handle menu opening and closing with proper state management
  • Style menu items with custom theming
  • Add keyboard navigation and accessibility features
  • Implement advanced patterns like nested menus and dividers
  • Optimize performance and handle edge cases

Understanding MUI's Menu Component

The Menu component in Material UI provides a temporary surface containing choices that appear when triggered by an anchor element. It's the perfect foundation for dropdown interfaces like navigation menus, settings panels, and user account menus.

Core Menu Concepts

The Menu component follows a specific pattern in MUI that's essential to understand before implementation:

  1. Anchor Element: The element (like a button) that triggers the menu to open
  2. Menu State: Boolean state to track if the menu is open or closed
  3. Position Management: Controls where the menu appears relative to the anchor
  4. Menu Items: Individual selectable options inside the menu

Let's explore the key props that make the Menu component work:

PropTypeDefaultDescription
openbooleanfalseControls whether the menu is displayed
anchorElHTMLElement | nullnullThe DOM element used to position the menu
onClosefunction-Callback fired when the menu is closed
childrennode-Menu contents, usually MenuItem components
anchorOriginobjectvertical: 'top', horizontal: 'left'Position of the menu relative to anchor
transformOriginobjectvertical: 'top', horizontal: 'left'Position for the menu's transform origin
autoFocusbooleantrueIf true, the menu will focus the first item when opened
MenuListPropsobjectProps applied to the MenuList element
elevationnumber8Shadow depth of the menu paper

Controlled vs. Uncontrolled Usage

The Menu component can be used in both controlled and uncontrolled patterns:

Controlled Menu: You manage the open/closed state explicitly with React state. This gives you complete control over when the menu appears and disappears.

Uncontrolled Menu: The component manages its own internal state. This is simpler but offers less control over the menu's behavior.

For most professional applications, I recommend using the controlled pattern as it provides more flexibility and predictability, especially when integrating with other state management systems.

Building the User Menu: Step-by-Step Guide

Let's build a professional dropdown user menu with an avatar. I'll break this down into manageable steps with explanations for each part of the implementation.

Step 1: Setting Up the Project

First, let's ensure we have the necessary dependencies installed:

npm install @mui/material @mui/icons-material @emotion/react @emotion/styled

For a user menu with avatars, we'll need these specific components:

import React, { useState } from 'react';
import { 
  Avatar,
  Button,
  Divider,
  IconButton,
  ListItemIcon,
  Menu,
  MenuItem,
  Tooltip,
  Typography
} from '@mui/material';
import {
  AccountCircle,
  Logout,
  PersonAdd,
  Settings
} from '@mui/icons-material';

These imports give us the core components we need: Menu for the dropdown container, MenuItem for individual options, Avatar for the user image, and various icons for menu actions.

Step 2: Creating the Basic Avatar Button

First, we'll create the avatar button that will serve as our anchor element for the menu:

function UserMenu() {
  // State to track whether menu is open
  const [anchorEl, setAnchorEl] = useState(null);
  const open = Boolean(anchorEl);
  
  // Handler for opening the menu
  const handleClick = (event) => {
    setAnchorEl(event.currentTarget);
  };
  
  // Handler for closing the menu
  const handleClose = () => {
    setAnchorEl(null);
  };
  
  return (
    <React.Fragment>
      <Tooltip title="Account settings">
        <IconButton
          onClick={handleClick}
          size="small"
          aria-controls={open ? 'account-menu' : undefined}
          aria-haspopup="true"
          aria-expanded={open ? 'true' : undefined}
        >
          <Avatar sx={{ width: 32, height: 32 }}>M</Avatar>
        </IconButton>
      </Tooltip>
      
      {/* Menu component will go here */}
    </React.Fragment>
  );
}

In this code:

  1. We create a state variable anchorEl to track the DOM element that will anchor our menu
  2. We derive the open state from the presence of an anchor element
  3. We set up handlers for opening and closing the menu
  4. We create an IconButton with an Avatar inside it, setting proper ARIA attributes for accessibility
  5. We wrap it in a Tooltip to provide additional context on hover

The Avatar component is initialized with just a letter "M" as a placeholder, but it could easily display an image of the user or their initials.

Step 3: Adding the Menu Component

Now, let's add the Menu component that will display when the avatar is clicked:

function UserMenu() {
  // Previous state and handlers remain the same...
  
  return (
    <React.Fragment>
      {/* Avatar button from previous step */}
      
      <Menu
        anchorEl={anchorEl}
        id="account-menu"
        open={open}
        onClose={handleClose}
        onClick={handleClose}
        PaperProps={{
          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' }}
      >
        {/* Menu items will go here */}
      </Menu>
    </React.Fragment>
  );
}

This code adds the Menu component with several important configurations:

  1. Anchor and State: We connect the menu to our avatar button using anchorEl and control its visibility with the open prop
  2. Positioning: We use transformOrigin and anchorOrigin to position the menu below and aligned to the right of the avatar
  3. Styling: We use the PaperProps to style the menu with a custom drop shadow and add a small triangular pointer at the top
  4. Behavior: We set up the menu to close when clicked or when the user clicks outside of it

The custom styling creates a more elegant, modern look than the default Material UI menu appearance.

Step 4: Adding Menu Items with Avatars and Icons

Now, let's populate our menu with items, including avatars and icons:

function UserMenu() {
  // Previous state and handlers remain the same...
  
  return (
    <React.Fragment>
      {/* Avatar button and Menu container from previous steps */}
      
      <Menu
        {/* Menu props from previous step */}
      >
        <MenuItem onClick={handleClose}>
          <Avatar /> My 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>
  );
}

In this step, we've added:

  1. Menu items with avatars for profile-related actions
  2. A divider to separate different categories of actions
  3. Menu items with icons for settings and system actions
  4. Click handlers that close the menu when an option is selected

The ListItemIcon component is used to properly align icons within menu items, ensuring consistent spacing and alignment.

Step 5: Enhancing with User Data

Let's improve our menu by integrating actual user data:

function UserMenu({ user }) {
  const [anchorEl, setAnchorEl] = useState(null);
  const open = Boolean(anchorEl);
  
  const handleClick = (event) => {
    setAnchorEl(event.currentTarget);
  };
  
  const handleClose = () => {
    setAnchorEl(null);
  };
  
  // Function to generate avatar content based on user data
  const getAvatarContent = () => {
    if (user?.photoUrl) {
      return <Avatar src={user.photoUrl} alt={user.name} sx={{ width: 32, height: 32 }} />;
    }
    
    // If no photo, use initials
    if (user?.name) {
      const initials = user.name
        .split(' ')
        .map(part => part[0])
        .join('')
        .toUpperCase()
        .substring(0, 2);
      
      return <Avatar sx={{ width: 32, height: 32 }}>{initials}</Avatar>;
    }
    
    // Fallback
    return <Avatar sx={{ width: 32, height: 32 }}><AccountCircle /></Avatar>;
  };
  
  return (
    <React.Fragment>
      <Tooltip title="Account settings">
        <IconButton
          onClick={handleClick}
          size="small"
          aria-controls={open ? 'account-menu' : undefined}
          aria-haspopup="true"
          aria-expanded={open ? 'true' : undefined}
        >
          {getAvatarContent()}
        </IconButton>
      </Tooltip>
      
      <Menu
        {/* Menu props from previous step */}
      >
        <MenuItem onClick={handleClose}>
          {getAvatarContent()}
          <Typography variant="body1" sx={{ ml: 1 }}>
            {user?.name || 'My Profile'}
          </Typography>
        </MenuItem>
        <MenuItem onClick={handleClose}>
          <ListItemIcon>
            <AccountCircle fontSize="small" />
          </ListItemIcon>
          {user?.email || 'My Account'}
        </MenuItem>
        {/* Other menu items remain the same */}
      </Menu>
    </React.Fragment>
  );
}

In this enhancement:

  1. We accept a user prop containing user information
  2. We create a getAvatarContent helper function that intelligently displays:
    • The user's photo if available
    • The user's initials if they have a name but no photo
    • A generic account icon as a fallback
  3. We display the user's name and email in the menu items if available
  4. We use Typography to ensure proper text styling

This approach makes the component more flexible and able to handle various user data scenarios.

Step 6: Adding Action Handlers

Let's make our menu functional by adding action handlers:

function UserMenu({ user, onLogout, onProfileClick, onSettingsClick }) {
  // Previous state and avatar logic remains the same...
  
  const handleProfileClick = () => {
    handleClose();
    if (onProfileClick) onProfileClick();
  };
  
  const handleSettingsClick = () => {
    handleClose();
    if (onSettingsClick) onSettingsClick();
  };
  
  const handleLogoutClick = () => {
    handleClose();
    if (onLogout) onLogout();
  };
  
  return (
    <React.Fragment>
      {/* Avatar button remains the same */}
      
      <Menu
        {/* Menu props remain the same */}
      >
        <MenuItem onClick={handleProfileClick}>
          {getAvatarContent()}
          <Typography variant="body1" sx={{ ml: 1 }}>
            {user?.name || 'My Profile'}
          </Typography>
        </MenuItem>
        {/* Other menu items with their respective handlers */}
        <MenuItem onClick={handleSettingsClick}>
          <ListItemIcon>
            <Settings fontSize="small" />
          </ListItemIcon>
          Settings
        </MenuItem>
        <MenuItem onClick={handleLogoutClick}>
          <ListItemIcon>
            <Logout fontSize="small" />
          </ListItemIcon>
          Logout
        </MenuItem>
      </Menu>
    </React.Fragment>
  );
}

In this step, we:

  1. Accept callback props for different menu actions
  2. Create handler functions that close the menu and then call the appropriate callback
  3. Connect these handlers to the respective menu items

This pattern allows the parent component to control what happens when menu items are clicked, making our UserMenu component reusable in different contexts.

Step 7: Enhancing Accessibility

Let's improve the accessibility of our menu:

function UserMenu({ user, onLogout, onProfileClick, onSettingsClick }) {
  // Previous state and handlers remain the same...
  
  return (
    <React.Fragment>
      <Tooltip title="Account settings">
        <IconButton
          onClick={handleClick}
          size="small"
          aria-controls={open ? 'account-menu' : undefined}
          aria-haspopup="true"
          aria-expanded={open ? 'true' : undefined}
          aria-label="User account menu"
        >
          {getAvatarContent()}
        </IconButton>
      </Tooltip>
      
      <Menu
        anchorEl={anchorEl}
        id="account-menu"
        open={open}
        onClose={handleClose}
        onClick={handleClose}
        PaperProps={{ /* Same as before */ }}
        transformOrigin={{ horizontal: 'right', vertical: 'top' }}
        anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
        MenuListProps={{
          'aria-labelledby': 'account-button',
          dense: true,
        }}
      >
        {/* Menu items remain the same */}
      </Menu>
    </React.Fragment>
  );
}

In this accessibility enhancement:

  1. We add an explicit aria-label to the IconButton to describe its purpose
  2. We use MenuListProps to set aria-labelledby which connects the menu to its trigger button
  3. We set dense: true to make the menu more compact and easier to navigate

These changes ensure that screen readers can properly announce the menu and its relationship to the button that opens it.

Step 8: Adding Animation and Transitions

Let's add smooth animations to our menu:

import { Fade } from '@mui/material';

function UserMenu({ user, onLogout, onProfileClick, onSettingsClick }) {
// Previous state and handlers remain the same...

return (
<React.Fragment>
{/* Avatar button remains the same */}

      <Menu
        anchorEl={anchorEl}
        id="account-menu"
        open={open}
        onClose={handleClose}
        onClick={handleClose}
        PaperProps={{ /* Same as before */ }}
        transformOrigin={{ horizontal: 'right', vertical: 'top' }}
        anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
        MenuListProps={{
          'aria-labelledby': 'account-button',
          dense: true,
        }}
        TransitionComponent={Fade}
        transitionDuration={250}
      >
        {/* Menu items remain the same */}
      </Menu>
    </React.Fragment>

);
}

In this step, we add:

  1. The TransitionComponent prop set to Fade for a smooth fade-in effect
  2. A custom transitionDuration to control the speed of the animation

These subtle animations make the interface feel more polished and responsive without being distracting.

Step 9: Putting It All Together

Now, let's combine everything into a complete, reusable component:

import React, { useState } from 'react';
import { 
  Avatar,
  Divider,
  Fade,
  IconButton,
  ListItemIcon,
  Menu,
  MenuItem,
  Tooltip,
  Typography
} from '@mui/material';
import {
  AccountCircle,
  Logout,
  PersonAdd,
  Settings
} from '@mui/icons-material';

function UserMenu({
user,
onLogout,
onProfileClick,
onSettingsClick,
onAddAccountClick
}) {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);

const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};

const handleClose = () => {
setAnchorEl(null);
};

// Generate avatar content based on user data
const getAvatarContent = () => {
if (user?.photoUrl) {
return <Avatar src={user.photoUrl} alt={user.name} sx={{ width: 32, height: 32 }} />;
}

    if (user?.name) {
      const initials = user.name
        .split(' ')
        .map(part => part[0])
        .join('')
        .toUpperCase()
        .substring(0, 2);

      return <Avatar sx={{ width: 32, height: 32 }}>{initials}</Avatar>;
    }

    return <Avatar sx={{ width: 32, height: 32 }}><AccountCircle /></Avatar>;

};

// Action handlers
const handleProfileClick = () => {
handleClose();
if (onProfileClick) onProfileClick();
};

const handleSettingsClick = () => {
handleClose();
if (onSettingsClick) onSettingsClick();
};

const handleAddAccountClick = () => {
handleClose();
if (onAddAccountClick) onAddAccountClick();
};

const handleLogoutClick = () => {
handleClose();
if (onLogout) onLogout();
};

return (
<React.Fragment>
<Tooltip title="Account settings">
<IconButton
onClick={handleClick}
size="small"
aria-controls={open ? 'account-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
aria-label="User account menu" >
{getAvatarContent()}
</IconButton>
</Tooltip>

      <Menu
        anchorEl={anchorEl}
        id="account-menu"
        open={open}
        onClose={handleClose}
        PaperProps={{
          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' }}
        MenuListProps={{
          'aria-labelledby': 'account-button',
          dense: true,
        }}
        TransitionComponent={Fade}
        transitionDuration={250}
      >
        <MenuItem onClick={handleProfileClick}>
          {getAvatarContent()}
          <Typography variant="body1" sx={{ ml: 1 }}>
            {user?.name || 'My Profile'}
          </Typography>
        </MenuItem>
        <MenuItem onClick={handleClose}>
          <ListItemIcon>
            <AccountCircle fontSize="small" />
          </ListItemIcon>
          {user?.email || 'My Account'}
        </MenuItem>
        <Divider />
        <MenuItem onClick={handleAddAccountClick}>
          <ListItemIcon>
            <PersonAdd fontSize="small" />
          </ListItemIcon>
          Add another account
        </MenuItem>
        <MenuItem onClick={handleSettingsClick}>
          <ListItemIcon>
            <Settings fontSize="small" />
          </ListItemIcon>
          Settings
        </MenuItem>
        <MenuItem onClick={handleLogoutClick}>
          <ListItemIcon>
            <Logout fontSize="small" />
          </ListItemIcon>
          Logout
        </MenuItem>
      </Menu>
    </React.Fragment>

);
}

export default UserMenu;

This complete component:

  1. Accepts user data and callback functions as props
  2. Intelligently displays user information when available
  3. Provides a clean, professional dropdown menu with avatars and icons
  4. Includes proper accessibility attributes
  5. Features smooth animations
  6. Handles all the state management internally

Advanced Customization Techniques

Let's explore some advanced customization options for our UserMenu component.

Theming the Menu

You can customize the appearance of the menu using MUI's theming system:

import { createTheme, ThemeProvider } from '@mui/material/styles';

// Create a custom theme for the menu
const menuTheme = createTheme({
components: {
MuiMenu: {
styleOverrides: {
paper: {
borderRadius: 8,
minWidth: 220,
},
},
},
MuiMenuItem: {
styleOverrides: {
root: {
padding: '10px 16px',
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.04)',
},
},
},
},
MuiAvatar: {
styleOverrides: {
root: {
backgroundColor: '#1976d2',
},
},
},
},
});

// Usage
function ThemedUserMenu(props) {
return (
<ThemeProvider theme={menuTheme}>
<UserMenu {...props} />
</ThemeProvider>
);
}

This theming approach allows you to:

  1. Customize the border radius of the menu
  2. Set a consistent minimum width
  3. Adjust padding and hover effects for menu items
  4. Set a default background color for avatars

Using Custom Styled Components

For more specific styling needs, you can use the styled API:

import { styled } from '@mui/material/styles';
import { Menu, MenuItem, Avatar } from '@mui/material';

// Create styled versions of MUI components
const StyledMenu = styled(Menu)(({ theme }) => ({
'& .MuiPaper-root': {
borderRadius: 8,
boxShadow: '0px 5px 15px rgba(0, 0, 0, 0.15)',
marginTop: theme.spacing(1.5),
},
}));

const StyledMenuItem = styled(MenuItem)(({ theme }) => ({
padding: theme.spacing(1.5, 2),
'&:hover': {
backgroundColor: theme.palette.action.hover,
},
}));

const UserAvatar = styled(Avatar)(({ theme }) => ({
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
fontSize: 14,
width: 32,
height: 32,
}));

// Then use these components in your UserMenu
function EnhancedUserMenu(props) {
// ... state and handlers

return (
<React.Fragment>
{/* ... */}
<StyledMenu
// ... other props >
<StyledMenuItem onClick={handleProfileClick}>
<UserAvatar>{/* ... */}</UserAvatar>
{/* ... */}
</StyledMenuItem>
{/* ... other menu items */}
</StyledMenu>
</React.Fragment>
);
}

This approach gives you even more control over the styling of each component, allowing for consistent theming that respects your application's design system.

Adding a Responsive Design

Let's make our menu responsive to different screen sizes:

import { useMediaQuery, useTheme } from '@mui/material';

function ResponsiveUserMenu(props) {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));

// ... state and handlers

return (
<React.Fragment>
<Tooltip title="Account settings">
<IconButton
// ... other props
size={isMobile ? "medium" : "small"} >
<Avatar
sx={{
              width: isMobile ? 40 : 32,
              height: isMobile ? 40 : 32
            }} >
{/* ... */}
</Avatar>
</IconButton>
</Tooltip>

      <Menu
        // ... other props
        PaperProps={{
          // ... other props
          sx: {
            // ... other styles
            width: isMobile ? '100%' : 'auto',
            maxWidth: isMobile ? '100%' : 320,
            left: isMobile ? 0 : 'auto',
            right: isMobile ? 0 : 'auto',
            // ... other styles
          },
        }}
        // Adjust positioning for mobile
        anchorOrigin={isMobile
          ? { horizontal: 'center', vertical: 'bottom' }
          : { horizontal: 'right', vertical: 'bottom' }
        }
        transformOrigin={isMobile
          ? { horizontal: 'center', vertical: 'top' }
          : { horizontal: 'right', vertical: 'top' }
        }
      >
        {/* ... menu items */}
      </Menu>
    </React.Fragment>

);
}

This responsive approach:

  1. Uses MUI's useMediaQuery hook to detect mobile screens
  2. Adjusts the size of the avatar button for better touch targets on mobile
  3. Makes the menu full-width on mobile devices
  4. Centers the menu on mobile for better visibility

Performance Optimization

Let's optimize our UserMenu component for better performance:

Memoizing the Component

import React, { useState, useCallback, memo } from 'react';

// Memoize the component to prevent unnecessary re-renders
const UserMenu = memo(function UserMenu({
user,
onLogout,
onProfileClick,
onSettingsClick
}) {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);

// Memoize handlers to prevent recreating them on each render
const handleClick = useCallback((event) => {
setAnchorEl(event.currentTarget);
}, []);

const handleClose = useCallback(() => {
setAnchorEl(null);
}, []);

const handleProfileClick = useCallback(() => {
handleClose();
if (onProfileClick) onProfileClick();
}, [handleClose, onProfileClick]);

// ... other memoized handlers

// Memoize the avatar content generation
const avatarContent = useMemo(() => {
// ... avatar content generation logic
return <Avatar>{/* ... */}</Avatar>;
}, [user?.photoUrl, user?.name]);

return (
// ... component JSX
);
});

These optimizations:

  1. Memoize the entire component to prevent re-rendering when parent components render
  2. Use useCallback to memoize event handlers
  3. Use useMemo for complex calculations like avatar content generation

Lazy Loading Menu Items

For menus with many items or complex content, you can implement lazy loading:

import React, { useState, lazy, Suspense } from 'react';
import { CircularProgress } from '@mui/material';

// Lazy load complex menu item components
const ProfileSection = lazy(() => import('./ProfileSection'));
const SettingsSection = lazy(() => import('./SettingsSection'));

function OptimizedUserMenu(props) {
// ... state and handlers

return (
<React.Fragment>
{/* ... button */}

      <Menu
        // ... menu props
      >
        <Suspense fallback={<MenuItem><CircularProgress size={20} /></MenuItem>}>
          <ProfileSection user={props.user} onProfileClick={props.onProfileClick} />
        </Suspense>

        <Divider />

        <Suspense fallback={<MenuItem><CircularProgress size={20} /></MenuItem>}>
          <SettingsSection onSettingsClick={props.onSettingsClick} />
        </Suspense>

        {/* ... other items */}
      </Menu>
    </React.Fragment>

);
}

This approach is especially useful for complex menus with nested components or data fetching requirements.

Best Practices and Common Issues

Best Practices

  1. Keep Menu Items Focused: Include only relevant actions in the user menu. Too many options can overwhelm users.

  2. Use Consistent Iconography: Maintain visual consistency by using icons from the same family and with similar visual weight.

  3. Group Related Actions: Use dividers to group related actions together, making the menu more scannable.

  4. Provide Visual Feedback: Ensure menu items have clear hover and active states to indicate interactivity.

  5. Mind the Mobile Experience: Ensure touch targets are large enough (at least 48px tall) on mobile devices.

  6. Test Keyboard Navigation: Verify that users can navigate the menu using keyboard controls (Tab, Enter, Escape, Arrow keys).

Common Issues and Solutions

Issue: Menu Positioning Problems

Problem: Menu appears in unexpected positions, especially in containers with overflow: hidden.

Solution:

<Menu
  // ... other props
  PopoverProps={{
    disableScrollLock: true,
    anchorReference: "anchorPosition",
    anchorPosition: { 
      top: anchorRect?.bottom || 0, 
      left: anchorRect?.right || 0 
    },
  }}
>
  {/* ... */}
</Menu>

Issue: Menu Closes Unexpectedly

Problem: Menu closes when clicking on elements inside menu items.

Solution:

<MenuItem
  onClick={(e) => {
    // Prevent the event from bubbling up to the Menu's onClick handler
    e.stopPropagation();
    handleSomeAction();
  }}
>
  {/* ... */}
</MenuItem>

Issue: Focus Management

Problem: Focus is lost when menu closes, creating accessibility issues.

Solution:

function UserMenu() {
  const buttonRef = useRef(null);
  
  const handleClose = () => {
    setAnchorEl(null);
    // Return focus to the button when the menu closes
    buttonRef.current?.focus();
  };
  
  return (
    <React.Fragment>
      <IconButton
        ref={buttonRef}
        // ... other props
      >
        {/* ... */}
      </IconButton>
      
      <Menu
        // ... other props
        onClose={handleClose}
      >
        {/* ... */}
      </Menu>
    </React.Fragment>
  );
}

Issue: Scrolling Issues on Mobile

Problem: Opening the menu causes unexpected page scrolling on mobile devices.

Solution:

<Menu
  // ... other props
  disableScrollLock={false}
  PopoverProps={{
    disableRestoreFocus: true,
  }}
>
  {/* ... */}
</Menu>

Advanced Features

Adding Badge Notifications

Let's enhance our user menu with notification badges:

import { Badge } from '@mui/material';

function UserMenuWithNotifications({ user, notificationCount, ...props }) {
// ... state and handlers

return (
<React.Fragment>
<Tooltip title="Account settings">
<IconButton>
<Badge
badgeContent={notificationCount}
color="error"
overlap="circular"
anchorOrigin={{
              vertical: 'top',
              horizontal: 'right',
            }} >
{getAvatarContent()}
</Badge>
</IconButton>
</Tooltip>

      <Menu
        // ... menu props
      >
        {/* ... menu items */}
        {notificationCount > 0 && (
          <MenuItem onClick={props.onViewNotifications}>
            <ListItemIcon>
              <Badge badgeContent={notificationCount} color="error">
                <NotificationsIcon fontSize="small" />
              </Badge>
            </ListItemIcon>
            View notifications
          </MenuItem>
        )}
        {/* ... other menu items */}
      </Menu>
    </React.Fragment>

);
}

This enhancement:

  1. Adds a notification badge to the avatar button
  2. Conditionally adds a notification menu item when notifications are present
  3. Uses the same badge styling for consistency

Adding Nested Submenus

For complex user menus, you might want to add nested submenus:

import React, { useState } from 'react';
import { 
  // ... other imports
  ArrowRight as ArrowRightIcon
} from '@mui/icons-material';

function UserMenuWithSubmenus(props) {
const [anchorEl, setAnchorEl] = useState(null);
const [settingsAnchorEl, setSettingsAnchorEl] = useState(null);

const open = Boolean(anchorEl);
const settingsOpen = Boolean(settingsAnchorEl);

// ... other handlers

const handleSettingsClick = (event) => {
// Prevent main menu from closing
event.stopPropagation();
setSettingsAnchorEl(event.currentTarget);
};

const handleSettingsClose = () => {
setSettingsAnchorEl(null);
};

return (
<React.Fragment>
{/* Main button */}
<Tooltip title="Account settings">
<IconButton
onClick={handleClick}
// ... other props >
{getAvatarContent()}
</IconButton>
</Tooltip>

      {/* Main menu */}
      <Menu
        // ... main menu props
      >
        {/* ... other menu items */}

        <MenuItem
          onClick={handleSettingsClick}
          sx={{
            '&.MuiMenuItem-root': {
              justifyContent: 'space-between',
            }
          }}
        >
          <Box sx={{ display: 'flex', alignItems: 'center' }}>
            <ListItemIcon>
              <Settings fontSize="small" />
            </ListItemIcon>
            Settings
          </Box>
          <ArrowRightIcon fontSize="small" sx={{ ml: 1 }} />
        </MenuItem>

        {/* ... other menu items */}
      </Menu>

      {/* Settings submenu */}
      <Menu
        anchorEl={settingsAnchorEl}
        open={settingsOpen}
        onClose={handleSettingsClose}
        anchorOrigin={{
          vertical: 'top',
          horizontal: 'right',
        }}
        transformOrigin={{
          vertical: 'top',
          horizontal: 'left',
        }}
      >
        <MenuItem onClick={() => { handleSettingsClose(); props.onAppearanceSettings(); }}>
          Appearance
        </MenuItem>
        <MenuItem onClick={() => { handleSettingsClose(); props.onPrivacySettings(); }}>
          Privacy & Security
        </MenuItem>
        <MenuItem onClick={() => { handleSettingsClose(); props.onNotificationSettings(); }}>
          Notifications
        </MenuItem>
      </Menu>
    </React.Fragment>

);
}

This implementation:

  1. Manages separate state for the main menu and submenu
  2. Positions the submenu to appear to the right of its parent item
  3. Prevents event propagation to keep the main menu open when opening the submenu
  4. Adds a visual indicator (arrow icon) to show that an item has a submenu

Real-World Integration Example

Let's see how our UserMenu component might be integrated into a real application:

import React, { useContext } from 'react';
import { AppBar, Toolbar, Typography, Box } from '@mui/material';
import { useNavigate } from 'react-router-dom';
import UserMenu from './UserMenu';
import { AuthContext } from '../contexts/AuthContext';
import { NotificationContext } from '../contexts/NotificationContext';

function AppHeader() {
const { user, logout } = useContext(AuthContext);
const { notifications } = useContext(NotificationContext);
const navigate = useNavigate();

const handleProfileClick = () => {
navigate('/profile');
};

const handleSettingsClick = () => {
navigate('/settings');
};

const handleAddAccountClick = () => {
navigate('/add-account');
};

const handleLogout = async () => {
try {
await logout();
navigate('/login');
} catch (error) {
console.error('Logout failed:', error);
}
};

return (
<AppBar position="static">
<Toolbar>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
My Application
</Typography>

        {user ? (
          <UserMenu
            user={user}
            notificationCount={notifications.length}
            onProfileClick={handleProfileClick}
            onSettingsClick={handleSettingsClick}
            onAddAccountClick={handleAddAccountClick}
            onLogout={handleLogout}
          />
        ) : (
          <Box>
            <Button color="inherit" onClick={() => navigate('/login')}>
              Login
            </Button>
            <Button color="inherit" onClick={() => navigate('/register')}>
              Register
            </Button>
          </Box>
        )}
      </Toolbar>
    </AppBar>

);
}

export default AppHeader;

This integration example:

  1. Uses React context to access user and notification data
  2. Uses React Router for navigation
  3. Handles authentication state to show either the user menu or login/register buttons
  4. Implements navigation and logout functionality

Wrapping Up

Building a dropdown user menu with avatars using MUI's Menu component is a practical skill for any React developer. We've covered everything from basic implementation to advanced customization, performance optimization, and real-world integration.

The approach we've taken leverages MUI's component system while adding our own enhancements for a polished user experience. By following the patterns in this guide, you can create user menus that are not only visually appealing but also accessible, performant, and maintainable.

Remember that a well-designed user menu is an important part of your application's user experience. It should be intuitive, responsive, and aligned with your overall design system. With the techniques covered in this article, you now have the knowledge to create professional user menus that enhance your React applications.