Building Navigation Menus with React MUI Material Icons: A Complete Guide
As a front-end developer working with React and Material UI, creating intuitive navigation is crucial for user experience. Material Icons provide a rich set of visual elements that can transform a plain navigation menu into something both functional and visually appealing. In this guide, I'll walk you through building a responsive navigation menu with icon labels using MUI components.
What You'll Learn
By the end of this tutorial, you'll know how to:
- Set up Material Icons in your React MUI project
- Create responsive navigation menus with icon labels
- Implement different navigation patterns (bottom, side, top navigation)
- Customize icon appearance and behavior
- Handle navigation states and active routes
- Apply accessibility best practices for icon-based navigation
Understanding Material Icons in MUI
Material Icons are an essential part of the Material Design system, providing visual cues that help users navigate interfaces intuitively. Before diving into implementation, let's understand what MUI offers in terms of icon components.
Material Icons Overview
MUI provides several ways to use icons in your React applications. The most common approach is through the @mui/icons-material
package, which contains over 2,000 pre-made icons following the Material Design guidelines. These icons are implemented as React components, making them easy to integrate into your JSX.
Each icon is available in multiple variants - filled (default), outlined, rounded, two-tone, and sharp. This variety gives you flexibility in matching your application's visual style while maintaining consistency.
Material Icons in MUI are SVG-based, which means they scale beautifully on all screen sizes and resolutions without pixelation. They also inherit color and size from their parent elements by default, making them easy to style within your design system.
Installation and Setup
To get started with Material Icons in your React MUI project, you'll need to install the necessary packages:
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled
Or if you're using Yarn:
yarn add @mui/material @mui/icons-material @emotion/react @emotion/styled
Once installed, you can import and use any icon from the package. For example:
import HomeIcon from '@mui/icons-material/Home';
import DashboardIcon from '@mui/icons-material/Dashboard';
import SettingsIcon from '@mui/icons-material/Settings';
function MyComponent() {
return (
<div>
<HomeIcon />
<DashboardIcon />
<SettingsIcon />
</div>
);
}
Material Icons Component Deep Dive
Material Icons in MUI are more than just visual elements - they're fully-featured React components with various props and customization options. Let's explore the capabilities of these icon components in detail.
Icon Component Props
Material Icons accept several props that allow you to control their appearance and behavior:
Prop | Type | Default | Description |
---|---|---|---|
color | string | 'inherit' | The color of the icon. Can be 'primary', 'secondary', 'action', 'error', 'disabled', or any custom color. |
fontSize | string | 'medium' | The size of the icon. Can be 'small', 'medium', 'large', or 'inherit'. |
sx | object | The system prop that allows defining system overrides as well as additional CSS styles. | |
htmlColor | string | undefined | Applies a color attribute to the SVG element. |
titleAccess | string | undefined | Provides a human-readable title for the element that contains the icon. |
viewBox | string | '0 0 24 24' | Defines the SVG viewBox. The viewBox attribute defines the position and dimension of an SVG viewport. |
Here's an example showing how to use these props:
import HomeIcon from '@mui/icons-material/Home';
function IconExample() {
return (
<div>
<HomeIcon color="primary" fontSize="large" />
<HomeIcon color="secondary" fontSize="small" />
<HomeIcon
sx={{
color: 'purple',
fontSize: 40,
'&:hover': {
color: 'orange',
}
}}
titleAccess="Home Page"
/>
</div>
);
}
Icon Variants
MUI Material Icons come in five distinct variants, each with its own visual style:
- Filled (Default): Solid icons with a filled appearance
- Outlined: Line-based icons with an outlined appearance
- Rounded: Icons with rounded corners
- Two-Tone: Two-color icons for added visual distinction
- Sharp: Icons with sharp, straight edges
You can import these variants by adjusting your import path:
// Default (Filled)
import HomeIcon from '@mui/icons-material/Home';
// Outlined variant
import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined';
// Rounded variant
import HomeRoundedIcon from '@mui/icons-material/HomeRounded';
// Two-tone variant
import HomeTwoToneIcon from '@mui/icons-material/HomeTwoTone';
// Sharp variant
import HomeSharpIcon from '@mui/icons-material/HomeSharp';
function IconVariants() {
return (
<div>
<HomeIcon /> {/* Filled (default) */}
<HomeOutlinedIcon />
<HomeRoundedIcon />
<HomeTwoToneIcon />
<HomeSharpIcon />
</div>
);
}
Customization Options
There are several ways to customize Material Icons in your MUI project:
1. Using the sx
prop
The sx
prop provides the most direct way to style icons with access to the theme:
import HomeIcon from '@mui/icons-material/Home';
function CustomizedIcon() {
return (
<HomeIcon
sx={{
color: 'primary.main',
fontSize: '2.5rem',
transform: 'rotate(10deg)',
transition: 'all 0.3s ease',
'&:hover': {
color: 'secondary.main',
transform: 'rotate(0deg) scale(1.2)',
}
}}
/>
);
}
2. Using the styled
API
For more complex styling or reusable styled icons:
import { styled } from '@mui/material/styles';
import HomeIcon from '@mui/icons-material/Home';
const AnimatedHomeIcon = styled(HomeIcon)(({ theme }) => ({
color: theme.palette.primary.main,
fontSize: '2rem',
transition: 'all 0.3s ease',
'&:hover': {
color: theme.palette.secondary.main,
transform: 'scale(1.2)',
},
}));
function StyledIconExample() {
return <AnimatedHomeIcon />;
}
3. Using Theme Overrides
You can also customize icons globally through the theme:
import { createTheme, ThemeProvider } from '@mui/material/styles';
import HomeIcon from '@mui/icons-material/Home';
const theme = createTheme({
components: {
MuiSvgIcon: {
styleOverrides: {
root: {
// Applied to all icons
fontSize: '1.5rem',
},
fontSizeSmall: {
// Applied when fontSize="small"
fontSize: '1rem',
},
fontSizeLarge: {
// Applied when fontSize="large"
fontSize: '2.5rem',
},
},
},
},
});
function ThemedIcons() {
return (
<ThemeProvider theme={theme}>
<HomeIcon />
<HomeIcon fontSize="small" />
<HomeIcon fontSize="large" />
</ThemeProvider>
);
}
Accessibility Considerations
Icons can pose accessibility challenges if not implemented correctly. Here are best practices to ensure your icon-based navigation is accessible:
- Always provide text labels alongside icons for clarity
- Use the
titleAccess
prop to add a title to the SVG for screen readers - Ensure sufficient color contrast between icons and their background
- Add
aria-label
when icons are used as interactive elements without visible text
import HomeIcon from '@mui/icons-material/Home';
import IconButton from '@mui/material/IconButton';
function AccessibleIcon() {
return (
<>
{/* For decorative icons with visible text */}
<button>
<HomeIcon titleAccess="Home" /> Home
</button>
{/* For interactive icons without visible text */}
<IconButton aria-label="Go to home page">
<HomeIcon />
</IconButton>
</>
);
}
Building Navigation Components with Material Icons
Now that we understand the basics of Material Icons in MUI, let's build different types of navigation menus that incorporate these icons with labels.
Bottom Navigation with Icons and Labels
Bottom navigation bars are common in mobile applications, providing easy access to key destinations. MUI's BottomNavigation
component is perfect for this pattern:
import React, { useState } from 'react';
import { Box, BottomNavigation, BottomNavigationAction } from '@mui/material';
import { Home as HomeIcon, Favorite as FavoriteIcon,
Person as PersonIcon, Settings as SettingsIcon } from '@mui/icons-material';
function BottomNav() {
const [value, setValue] = useState(0);
return (
<Box sx={{ width: '100%', position: 'fixed', bottom: 0 }}>
<BottomNavigation
showLabels
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
sx={{
'& .MuiBottomNavigationAction-root': {
minWidth: 'auto',
padding: '6px 0',
},
'& .Mui-selected': {
color: 'primary.main',
}
}}
>
<BottomNavigationAction label="Home" icon={<HomeIcon />} />
<BottomNavigationAction label="Favorites" icon={<FavoriteIcon />} />
<BottomNavigationAction label="Profile" icon={<PersonIcon />} />
<BottomNavigationAction label="Settings" icon={<SettingsIcon />} />
</BottomNavigation>
</Box>
);
}
In this example:
- We create a bottom navigation bar with four items
- Each item has an icon and a label
- The
showLabels
prop ensures labels are always visible - We track the active tab with React state
- Custom styling makes the active item visually distinct
Responsive Side Navigation with Icons and Labels
For desktop applications, a side navigation drawer is often more appropriate. Let's build a responsive drawer that collapses to just icons on smaller screens:
import React, { useState } from 'react';
import {
Box,
Drawer,
List,
ListItem,
ListItemIcon,
ListItemText,
IconButton,
Divider,
useMediaQuery,
useTheme
} from '@mui/material';
import {
Menu as MenuIcon,
Home as HomeIcon,
Dashboard as DashboardIcon,
People as PeopleIcon,
BarChart as AnalyticsIcon,
Settings as SettingsIcon,
ChevronLeft as ChevronLeftIcon
} from '@mui/icons-material';
function SideNavigation() {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const [open, setOpen] = useState(!isMobile);
const toggleDrawer = () => {
setOpen(!open);
};
const drawerWidth = open ? 240 : 73;
const menuItems = [
{ text: 'Home', icon: <HomeIcon /> },
{ text: 'Dashboard', icon: <DashboardIcon /> },
{ text: 'Users', icon: <PeopleIcon /> },
{ text: 'Analytics', icon: <AnalyticsIcon /> },
{ text: 'Settings', icon: <SettingsIcon /> }
];
return (
<>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={toggleDrawer}
edge="start"
sx={{ mr: 2, display: { sm: 'none' } }}
>
<MenuIcon />
</IconButton>
<Drawer
variant={isMobile ? 'temporary' : 'permanent'}
open={isMobile ? open : true}
onClose={isMobile ? toggleDrawer : undefined}
sx={{
width: drawerWidth,
flexShrink: 0,
'& .MuiDrawer-paper': {
width: drawerWidth,
boxSizing: 'border-box',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: 'hidden',
},
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', p: 1 }}>
{!isMobile && (
<IconButton onClick={toggleDrawer}>
<ChevronLeftIcon />
</IconButton>
)}
</Box>
<Divider />
<List>
{menuItems.map((item) => (
<ListItem
button
key={item.text}
sx={{
minHeight: 48,
justifyContent: open ? 'initial' : 'center',
px: 2.5,
}}
>
<ListItemIcon
sx={{
minWidth: 0,
mr: open ? 3 : 'auto',
justifyContent: 'center',
}}
>
{item.icon}
</ListItemIcon>
<ListItemText
primary={item.text}
sx={{ opacity: open ? 1 : 0 }}
/>
</ListItem>
))}
</List>
</Drawer>
</>
);
}
This side navigation:
- Adapts to screen size (temporary drawer on mobile, permanent on desktop)
- Toggles between expanded (with text labels) and collapsed (icons only) states
- Uses
ListItemIcon
andListItemText
for proper alignment - Animates smoothly between states using MUI transitions
Top Navigation Bar with Icon Buttons
For top navigation patterns, we can use the AppBar
component combined with icon buttons:
import React, { useState } from 'react';
import {
AppBar,
Toolbar,
Typography,
IconButton,
Box,
Tooltip,
Badge,
Avatar,
Menu,
MenuItem
} from '@mui/material';
import {
Menu as MenuIcon,
Notifications as NotificationsIcon,
Mail as MailIcon,
MoreVert as MoreIcon
} from '@mui/icons-material';
function TopNavigation() {
const [anchorEl, setAnchorEl] = useState(null);
const [mobileMoreAnchorEl, setMobileMoreAnchorEl] = useState(null);
const isMenuOpen = Boolean(anchorEl);
const isMobileMenuOpen = Boolean(mobileMoreAnchorEl);
const handleProfileMenuOpen = (event) => {
setAnchorEl(event.currentTarget);
};
const handleMobileMenuClose = () => {
setMobileMoreAnchorEl(null);
};
const handleMenuClose = () => {
setAnchorEl(null);
handleMobileMenuClose();
};
const handleMobileMenuOpen = (event) => {
setMobileMoreAnchorEl(event.currentTarget);
};
const menuId = 'primary-search-account-menu';
const renderMenu = (
<Menu
anchorEl={anchorEl}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
id={menuId}
keepMounted
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
open={isMenuOpen}
onClose={handleMenuClose}
>
<MenuItem onClick={handleMenuClose}>Profile</MenuItem>
<MenuItem onClick={handleMenuClose}>My account</MenuItem>
<MenuItem onClick={handleMenuClose}>Logout</MenuItem>
</Menu>
);
const mobileMenuId = 'primary-search-account-menu-mobile';
const renderMobileMenu = (
<Menu
anchorEl={mobileMoreAnchorEl}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
id={mobileMenuId}
keepMounted
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
open={isMobileMenuOpen}
onClose={handleMobileMenuClose}
>
<MenuItem>
<IconButton size="large" aria-label="show 4 new mails" color="inherit">
<Badge badgeContent={4} color="error">
<MailIcon />
</Badge>
</IconButton>
<p>Messages</p>
</MenuItem>
<MenuItem>
<IconButton size="large" aria-label="show 17 new notifications" color="inherit">
<Badge badgeContent={17} color="error">
<NotificationsIcon />
</Badge>
</IconButton>
<p>Notifications</p>
</MenuItem>
<MenuItem onClick={handleProfileMenuOpen}>
<IconButton
size="large"
aria-label="account of current user"
aria-controls="primary-search-account-menu"
aria-haspopup="true"
color="inherit"
>
<Avatar sx={{ width: 24, height: 24 }}>U</Avatar>
</IconButton>
<p>Profile</p>
</MenuItem>
</Menu>
);
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
<Toolbar>
<IconButton
size="large"
edge="start"
color="inherit"
aria-label="open drawer"
sx={{ mr: 2 }}
>
<MenuIcon />
</IconButton>
<Typography
variant="h6"
noWrap
component="div"
sx={{ display: { xs: 'none', sm: 'block' } }}
>
MUI APP
</Typography>
<Box sx={{ flexGrow: 1 }} />
<Box sx={{ display: { xs: 'none', md: 'flex' } }}>
<Tooltip title="Messages">
<IconButton size="large" aria-label="show 4 new mails" color="inherit">
<Badge badgeContent={4} color="error">
<MailIcon />
</Badge>
</IconButton>
</Tooltip>
<Tooltip title="Notifications">
<IconButton size="large" aria-label="show 17 new notifications" color="inherit">
<Badge badgeContent={17} color="error">
<NotificationsIcon />
</Badge>
</IconButton>
</Tooltip>
<Tooltip title="Account">
<IconButton
size="large"
edge="end"
aria-label="account of current user"
aria-controls={menuId}
aria-haspopup="true"
onClick={handleProfileMenuOpen}
color="inherit"
>
<Avatar sx={{ width: 32, height: 32 }}>U</Avatar>
</IconButton>
</Tooltip>
</Box>
<Box sx={{ display: { xs: 'flex', md: 'none' } }}>
<IconButton
size="large"
aria-label="show more"
aria-controls={mobileMenuId}
aria-haspopup="true"
onClick={handleMobileMenuOpen}
color="inherit"
>
<MoreIcon />
</IconButton>
</Box>
</Toolbar>
</AppBar>
{renderMobileMenu}
{renderMenu}
</Box>
);
}
This top navigation:
- Uses icon buttons with tooltips for desktop navigation
- Adds badges to show notification counts
- Collapses to a "more" menu on mobile screens
- Includes dropdown menus for additional options
- Uses the Avatar component for the user profile section
Creating a Complete Responsive Navigation System
Now, let's combine these patterns to create a complete responsive navigation system with Material Icons. This example includes:
- Top AppBar with icon buttons
- Responsive side drawer that can collapse to icons-only
- Bottom navigation for mobile devices
import React, { useState } from 'react';
import {
AppBar,
Box,
Toolbar,
IconButton,
Typography,
Drawer,
List,
ListItem,
ListItemIcon,
ListItemText,
Divider,
BottomNavigation,
BottomNavigationAction,
useMediaQuery,
useTheme,
CssBaseline,
Tooltip,
Badge
} from '@mui/material';
import {
Menu as MenuIcon,
ChevronLeft as ChevronLeftIcon,
Home as HomeIcon,
Dashboard as DashboardIcon,
People as PeopleIcon,
BarChart as AnalyticsIcon,
Settings as SettingsIcon,
Notifications as NotificationsIcon,
Mail as MailIcon,
Favorite as FavoriteIcon
} from '@mui/icons-material';
function ResponsiveNavigation() {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const isTablet = useMediaQuery(theme.breakpoints.down('md'));
const [drawerOpen, setDrawerOpen] = useState(!isTablet);
const [bottomNavValue, setBottomNavValue] = useState(0);
const handleDrawerToggle = () => {
setDrawerOpen(!drawerOpen);
};
const drawerWidth = drawerOpen ? 240 : 73;
const menuItems = [
{ text: 'Home', icon: <HomeIcon />, value: 0 },
{ text: 'Dashboard', icon: <DashboardIcon />, value: 1 },
{ text: 'Users', icon: <PeopleIcon />, value: 2 },
{ text: 'Analytics', icon: <AnalyticsIcon />, value: 3 },
{ text: 'Settings', icon: <SettingsIcon />, value: 4 }
];
// Main content area that adjusts based on navigation state
const contentOffset = isMobile ? 0 : drawerWidth;
const contentBottomPadding = isMobile ? 56 : 0; // Height of bottom nav
return (
<Box sx={{ display: 'flex', height: '100vh' }}>
<CssBaseline />
{/* Top AppBar */}
<AppBar
position="fixed"
sx={{
width: { sm: `calc(100% - ${contentOffset}px)` },
ml: { sm: `${contentOffset}px` },
zIndex: (theme) => theme.zIndex.drawer + 1
}}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
sx={{ mr: 2, display: { sm: 'none' } }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
MUI Navigation
</Typography>
{/* Top Navigation Icons */}
<Box sx={{ display: 'flex' }}>
<Tooltip title="Messages">
<IconButton color="inherit">
<Badge badgeContent={4} color="error">
<MailIcon />
</Badge>
</IconButton>
</Tooltip>
<Tooltip title="Notifications">
<IconButton color="inherit">
<Badge badgeContent={17} color="error">
<NotificationsIcon />
</Badge>
</IconButton>
</Tooltip>
</Box>
</Toolbar>
</AppBar>
{/* Side Drawer Navigation */}
<Drawer
variant={isMobile ? 'temporary' : 'permanent'}
open={isMobile ? drawerOpen : true}
onClose={isMobile ? handleDrawerToggle : undefined}
sx={{
width: drawerWidth,
flexShrink: 0,
'& .MuiDrawer-paper': {
width: drawerWidth,
boxSizing: 'border-box',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: 'hidden',
},
}}
>
<Toolbar /> {/* Spacer to align content below AppBar */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', p: 1 }}>
{!isMobile && (
<IconButton onClick={handleDrawerToggle}>
<ChevronLeftIcon />
</IconButton>
)}
</Box>
<Divider />
<List>
{menuItems.map((item) => (
<ListItem
button
key={item.text}
selected={bottomNavValue === item.value}
onClick={() => setBottomNavValue(item.value)}
sx={{
minHeight: 48,
justifyContent: drawerOpen ? 'initial' : 'center',
px: 2.5,
'&.Mui-selected': {
bgcolor: 'rgba(0, 0, 0, 0.08)',
'& .MuiListItemIcon-root': {
color: 'primary.main',
},
'& .MuiListItemText-primary': {
color: 'primary.main',
},
},
}}
>
<ListItemIcon
sx={{
minWidth: 0,
mr: drawerOpen ? 3 : 'auto',
justifyContent: 'center',
}}
>
{item.icon}
</ListItemIcon>
<ListItemText
primary={item.text}
sx={{
opacity: drawerOpen ? 1 : 0,
transition: theme.transitions.create('opacity', {
duration: theme.transitions.duration.shortest,
})
}}
/>
</ListItem>
))}
</List>
</Drawer>
{/* Main Content */}
<Box
component="main"
sx={{
flexGrow: 1,
p: 3,
width: { sm: `calc(100% - ${contentOffset}px)` },
ml: { sm: `${contentOffset}px` },
mt: { xs: '56px', sm: '64px' },
mb: { xs: `${contentBottomPadding}px` },
transition: theme.transitions.create(['width', 'margin-left'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
}}
>
{/* Page content would go here */}
<Typography paragraph>
Select a navigation item to see content for: {menuItems[bottomNavValue]?.text}
</Typography>
</Box>
{/* Bottom Navigation (Mobile Only) */}
{isMobile && (
<BottomNavigation
value={bottomNavValue}
onChange={(event, newValue) => {
setBottomNavValue(newValue);
}}
showLabels
sx={{
width: '100%',
position: 'fixed',
bottom: 0,
zIndex: theme.zIndex.appBar,
borderTop: '1px solid',
borderColor: 'divider',
}}
>
{menuItems.map((item) => (
<BottomNavigationAction
key={item.text}
label={item.text}
icon={item.icon}
/>
))}
</BottomNavigation>
)}
</Box>
);
}
This comprehensive navigation system:
- Adapts to different screen sizes
- Provides consistent navigation options across devices
- Uses the same icons and labels in all navigation patterns
- Maintains state across different navigation components
- Provides visual feedback for the active/selected item
- Adjusts content area based on the navigation state
Advanced Navigation Techniques with Material Icons
Let's explore some advanced techniques for enhancing your navigation menus with Material Icons.
Custom Icon Badges and Indicators
You can create custom badges and indicators to show status information:
import React from 'react';
import { Box, Badge, styled } from '@mui/material';
import {
Mail as MailIcon,
Chat as ChatIcon,
Notifications as NotificationsIcon,
AccountCircle as AccountCircleIcon
} from '@mui/icons-material';
// Custom styled badge for online status
const OnlineBadge = styled(Badge)(({ theme }) => ({
'& .MuiBadge-badge': {
backgroundColor: '#44b700',
color: '#44b700',
boxShadow: `0 0 0 2px ${theme.palette.background.paper}`,
'&::after': {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
borderRadius: '50%',
animation: 'ripple 1.2s infinite ease-in-out',
border: '1px solid currentColor',
content: '""',
},
},
'@keyframes ripple': {
'0%': {
transform: 'scale(.8)',
opacity: 1,
},
'100%': {
transform: 'scale(2.4)',
opacity: 0,
},
},
}));
// Custom styled badge for different status types
const StatusBadge = styled(Badge)(({ theme, status }) => ({
'& .MuiBadge-badge': {
backgroundColor:
status === 'online' ? '#44b700' :
status === 'away' ? '#ffc107' :
status === 'busy' ? '#f44336' :
'#bdbdbd',
boxShadow: `0 0 0 2px ${theme.palette.background.paper}`,
width: 12,
height: 12,
borderRadius: '50%',
},
}));
function AdvancedBadges() {
return (
<Box sx={{ display: 'flex', gap: 4, p: 2 }}>
{/* Standard notification badge */}
<Badge badgeContent={4} color="primary">
<MailIcon fontSize="large" />
</Badge>
{/* Dot badge */}
<Badge variant="dot" color="error">
<NotificationsIcon fontSize="large" />
</Badge>
{/* Animated online status badge */}
<OnlineBadge
overlap="circular"
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
variant="dot"
>
<ChatIcon fontSize="large" />
</OnlineBadge>
{/* Different status indicators */}
<StatusBadge
overlap="circular"
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
variant="dot"
status="online"
>
<AccountCircleIcon fontSize="large" />
</StatusBadge>
<StatusBadge
overlap="circular"
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
variant="dot"
status="away"
>
<AccountCircleIcon fontSize="large" />
</StatusBadge>
<StatusBadge
overlap="circular"
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
variant="dot"
status="busy"
>
<AccountCircleIcon fontSize="large" />
</StatusBadge>
</Box>
);
}
Animated Icon Navigation
Adding animations to your navigation icons can enhance the user experience:
import React, { useState } from 'react';
import { Box, IconButton, styled } from '@mui/material';
import {
Home as HomeIcon,
Explore as ExploreIcon,
AddCircleOutline as AddIcon,
Notifications as NotificationsIcon,
Person as PersonIcon
} from '@mui/icons-material';
const AnimatedIconButton = styled(IconButton)(({ theme, active }) => ({
color: active ? theme.palette.primary.main : theme.palette.text.secondary,
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
transform: active ? 'translateY(-5px)' : 'translateY(0)',
'& svg': {
fontSize: active ? 32 : 24,
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
}
}));
function AnimatedNavigation() {
const [activeIndex, setActiveIndex] = useState(0);
const navItems = [
{ icon: <HomeIcon />, label: 'Home' },
{ icon: <ExploreIcon />, label: 'Explore' },
{ icon: <AddIcon />, label: 'Create' },
{ icon: <NotificationsIcon />, label: 'Notifications' },
{ icon: <PersonIcon />, label: 'Profile' }
];
return (
<Box
sx={{
display: 'flex',
justifyContent: 'space-around',
width: '100%',
maxWidth: 500,
margin: '0 auto',
bgcolor: 'background.paper',
borderRadius: 2,
py: 1,
boxShadow: 3
}}
>
{navItems.map((item, index) => (
<Box
key={index}
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
position: 'relative'
}}
>
<AnimatedIconButton
active={activeIndex === index ? 1 : 0}
onClick={() => setActiveIndex(index)}
aria-label={item.label}
>
{item.icon}
</AnimatedIconButton>
{/* Indicator dot */}
<Box
sx={{
width: 4,
height: 4,
borderRadius: '50%',
bgcolor: 'primary.main',
opacity: activeIndex === index ? 1 : 0,
transition: 'opacity 0.3s'
}}
/>
</Box>
))}
</Box>
);
}
Navigation with Dynamic Icon Coloring
You can create navigation with dynamic icon coloring based on the active state:
import React, { useState } from 'react';
import { Box, Tab, Tabs, useTheme } from '@mui/material';
import {
Home as HomeIcon,
Dashboard as DashboardIcon,
BookmarkBorder as BookmarkIcon,
ShoppingCart as CartIcon,
Person as ProfileIcon
} from '@mui/icons-material';
function DynamicColorNavigation() {
const theme = useTheme();
const [value, setValue] = useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
// Define tab colors
const tabColors = [
theme.palette.primary.main,
theme.palette.secondary.main,
'#009688', // teal
'#ff9800', // orange
'#9c27b0' // purple
];
return (
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
<Tabs
value={value}
onChange={handleChange}
variant="fullWidth"
TabIndicatorProps={{
style: { background: tabColors[value] }
}}
sx={{
'& .MuiTab-root': {
minHeight: 64,
},
'& .Mui-selected': {
color: tabColors[value],
transition: 'color 0.3s ease',
},
'& .MuiTabs-indicator': {
transition: 'background-color 0.3s ease',
}
}}
>
<Tab
icon={<HomeIcon />}
label="Home"
iconPosition="top"
sx={{
'& .MuiSvgIcon-root': {
fontSize: 28,
transition: 'color 0.3s ease',
color: value === 0 ? tabColors[0] : 'action.active',
}
}}
/>
<Tab
icon={<DashboardIcon />}
label="Dashboard"
iconPosition="top"
sx={{
'& .MuiSvgIcon-root': {
fontSize: 28,
transition: 'color 0.3s ease',
color: value === 1 ? tabColors[1] : 'action.active',
}
}}
/>
<Tab
icon={<BookmarkIcon />}
label="Bookmarks"
iconPosition="top"
sx={{
'& .MuiSvgIcon-root': {
fontSize: 28,
transition: 'color 0.3s ease',
color: value === 2 ? tabColors[2] : 'action.active',
}
}}
/>
<Tab
icon={<CartIcon />}
label="Cart"
iconPosition="top"
sx={{
'& .MuiSvgIcon-root': {
fontSize: 28,
transition: 'color 0.3s ease',
color: value === 3 ? tabColors[3] : 'action.active',
}
}}
/>
<Tab
icon={<ProfileIcon />}
label="Profile"
iconPosition="top"
sx={{
'& .MuiSvgIcon-root': {
fontSize: 28,
transition: 'color 0.3s ease',
color: value === 4 ? tabColors[4] : 'action.active',
}
}}
/>
</Tabs>
</Box>
);
}
Integration with React Router
To make our navigation functional, we should integrate it with React Router. Here's how to create a router-aware navigation system:
import React from 'react';
import { BrowserRouter, Routes, Route, Link, useLocation } from 'react-router-dom';
import {
Box,
List,
ListItem,
ListItemIcon,
ListItemText,
Drawer,
AppBar,
Toolbar,
Typography,
IconButton,
useMediaQuery,
useTheme,
BottomNavigation,
BottomNavigationAction
} from '@mui/material';
import {
Home as HomeIcon,
Dashboard as DashboardIcon,
People as PeopleIcon,
Settings as SettingsIcon,
Menu as MenuIcon
} from '@mui/icons-material';
// Home page component
function HomePage() {
return <Typography variant="h4" component="h1" gutterBottom>Home Page</Typography>;
}
// Dashboard page component
function DashboardPage() {
return <Typography variant="h4" component="h1" gutterBottom>Dashboard Page</Typography>;
}
// Users page component
function UsersPage() {
return <Typography variant="h4" component="h1" gutterBottom>Users Page</Typography>;
}
// Settings page component
function SettingsPage() {
return <Typography variant="h4" component="h1" gutterBottom>Settings Page</Typography>;
}
// Navigation component that's aware of the current route
function NavigationMenu() {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const [drawerOpen, setDrawerOpen] = React.useState(false);
const location = useLocation();
const handleDrawerToggle = () => {
setDrawerOpen(!drawerOpen);
};
const routes = [
{ path: '/', label: 'Home', icon: <HomeIcon /> },
{ path: '/dashboard', label: 'Dashboard', icon: <DashboardIcon /> },
{ path: '/users', label: 'Users', icon: <PeopleIcon /> },
{ path: '/settings', label: 'Settings', icon: <SettingsIcon /> }
];
// Get current path to determine active route
const currentPath = location.pathname;
const currentRouteIndex = routes.findIndex(route => route.path === currentPath);
return (
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
<AppBar position="fixed">
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
sx={{ mr: 2, display: { sm: 'none' } }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
MUI Router Navigation
</Typography>
</Toolbar>
</AppBar>
{/* Side Drawer for larger screens */}
<Box sx={{ display: 'flex', flex: 1 }}>
<Drawer
variant={isMobile ? 'temporary' : 'permanent'}
open={isMobile ? drawerOpen : true}
onClose={isMobile ? handleDrawerToggle : undefined}
sx={{
width: 240,
flexShrink: 0,
'& .MuiDrawer-paper': {
width: 240,
boxSizing: 'border-box',
top: ['56px', '64px'],
height: 'auto',
bottom: 0,
},
display: { xs: isMobile ? 'block' : 'none', sm: 'block' }
}}
>
<List>
{routes.map((route) => (
<ListItem
button
key={route.path}
component={Link}
to={route.path}
selected={currentPath === route.path}
sx={{
'&.Mui-selected': {
bgcolor: 'action.selected',
'& .MuiListItemIcon-root': {
color: 'primary.main',
},
},
}}
>
<ListItemIcon>{route.icon}</ListItemIcon>
<ListItemText primary={route.label} />
</ListItem>
))}
</List>
</Drawer>
{/* Main content */}
<Box
component="main"
sx={{
flexGrow: 1,
p: 3,
width: { sm: 'calc(100% - 240px)' },
mt: ['56px', '64px'],
mb: { xs: '56px', sm: 0 }
}}
>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/dashboard" element={<DashboardPage />} />
<Route path="/users" element={<UsersPage />} />
<Route path="/settings" element={<SettingsPage />} />
</Routes>
</Box>
</Box>
{/* Bottom Navigation for mobile */}
{isMobile && (
<BottomNavigation
value={currentRouteIndex !== -1 ? currentRouteIndex : 0}
showLabels
sx={{
width: '100%',
position: 'fixed',
bottom: 0,
borderTop: '1px solid',
borderColor: 'divider',
}}
>
{routes.map((route) => (
<BottomNavigationAction
key={route.path}
label={route.label}
icon={route.icon}
component={Link}
to={route.path}
/>
))}
</BottomNavigation>
)}
</Box>
);
}
// Main App with Router
function RouterNavigation() {
return (
<BrowserRouter>
<NavigationMenu />
</BrowserRouter>
);
}
export default RouterNavigation;
Best Practices for Icon Navigation
Based on my experience building navigation systems with Material Icons, here are some best practices to follow:
1. Maintain Consistency
Keep your icon usage consistent throughout your application. This means:
- Use the same icon style (filled, outlined, etc.) across your navigation
- Maintain consistent sizing and positioning
- Use the same visual indicators for active/selected states
2. Prioritize Accessibility
Make your icon navigation accessible to all users:
- Always include text labels with icons when possible
- Use
aria-label
attributes for icon-only buttons - Ensure sufficient color contrast for icons
- Test keyboard navigation through your menu items
- Add focus styles for keyboard users
3. Optimize for Performance
Large icon libraries can impact performance:
// DON'T: Import the entire icons library
import * as Icons from '@mui/icons-material';
// DO: Import only the icons you need
import HomeIcon from '@mui/icons-material/Home';
import DashboardIcon from '@mui/icons-material/Dashboard';
4. Handle Different Screen Sizes
Design your navigation to adapt to different screen sizes:
- Use different navigation patterns based on screen size
- Collapse text labels on smaller screens
- Ensure touch targets are large enough on mobile (at least 48x48px)
- Test your navigation on various devices
5. Add Visual Feedback
Provide visual feedback for user interactions:
- Highlight the active/current page
- Add hover and focus states
- Include transitions for smooth state changes
- Use badges to indicate notifications or updates
Common Issues and Solutions
When implementing icon navigation with MUI, you might encounter these common issues:
1. Icons Not Rendering Correctly
Problem: Icons appear too large, too small, or with incorrect colors.
Solution: Make sure you're properly setting the fontSize
and color
props:
// Fix for size issues
<HomeIcon fontSize="small" /> // small size
<HomeIcon fontSize="medium" /> // default size
<HomeIcon fontSize="large" /> // large size
<HomeIcon sx={{ fontSize: 40 }} /> // custom size
// Fix for color issues
<HomeIcon color="primary" /> // theme primary color
<HomeIcon color="secondary" /> // theme secondary color
<HomeIcon color="action" /> // default icon color
<HomeIcon sx={{ color: '#ff5722' }} /> // custom color
2. Inconsistent Spacing in Navigation Items
Problem: Navigation items have inconsistent spacing or alignment.
Solution: Use consistent padding and margin values, and leverage flexbox for alignment:
<ListItem
sx={{
px: 2, // Consistent horizontal padding
py: 1.5, // Consistent vertical padding
display: 'flex',
alignItems: 'center',
}}
>
<ListItemIcon sx={{ minWidth: 40 }}> {/* Fixed width for icons */}
<HomeIcon />
</ListItemIcon>
<ListItemText primary="Home" />
</ListItem>
3. Poor Mobile Experience
Problem: Navigation is difficult to use on mobile devices.
Solution: Implement responsive navigation patterns and ensure touch targets are large enough:
<IconButton
sx={{
p: 1.5, // Increased padding for better touch target
'& .MuiSvgIcon-root': {
fontSize: 28, // Larger icon for better visibility
},
}}
>
<HomeIcon />
</IconButton>
4. Performance Issues with Many Icons
Problem: Application performance degrades when using many icons.
Solution: Use code splitting and dynamic imports to load icons only when needed:
import React, { lazy, Suspense } from 'react';
// Dynamically import icons
const HomeIcon = lazy(() => import('@mui/icons-material/Home'));
const DashboardIcon = lazy(() => import('@mui/icons-material/Dashboard'));
function LazyIconNavigation() {
return (
<nav>
<Suspense fallback={<div>Loading...</div>}>
<HomeIcon />
<DashboardIcon />
</Suspense>
</nav>
);
}
Wrapping Up
Creating navigation menus with Material Icons in React MUI offers a powerful way to enhance your application's user experience. We've covered everything from basic implementation to advanced techniques, including responsive design patterns, animation, and integration with routing libraries.
By following the best practices outlined in this guide, you can create intuitive, accessible, and visually appealing navigation systems that work across all devices. Remember to prioritize consistency, accessibility, and performance as you implement your icon-based navigation.
The combination of MUI's powerful components and Material Icons provides all the tools you need to build professional navigation experiences that users will find both beautiful and functional.