Building a Mobile Tab Bar Navigation with React MUI Bottom Navigation
Mobile applications often feature a tab bar at the bottom of the screen for easy navigation between main sections. If you're building a React application with Material UI (MUI), the BottomNavigation component provides an elegant solution for implementing this pattern. In this guide, I'll walk you through creating a responsive, accessible, and customizable mobile tab bar navigation using MUI's BottomNavigation component.
What You'll Learn
By the end of this article, you'll know how to:
- Implement a mobile-friendly tab bar navigation using MUI's BottomNavigation
- Configure navigation items with icons and labels
- Handle state management for navigation
- Style and customize the bottom navigation to match your design
- Integrate the navigation with React Router
- Implement advanced patterns like conditional rendering and badges
- Optimize performance and accessibility
Understanding MUI's BottomNavigation Component
Before diving into implementation, let's understand what the BottomNavigation component is and how it works within the MUI ecosystem.
The BottomNavigation component in Material UI provides a navigation pattern commonly used in mobile applications. It's typically fixed to the bottom of the screen and contains multiple navigation items represented by icons and optional labels. This component follows the Material Design guidelines for bottom navigation, providing a familiar and intuitive interface for users.
The primary use case for BottomNavigation is to provide quick access to the top-level destinations in your application. It's especially useful for mobile interfaces where screen real estate is limited, and reaching the top of the screen with one hand can be difficult.
Component Structure
MUI's bottom navigation consists of two main components:
BottomNavigation
: The container component that manages the overall state and appearanceBottomNavigationAction
: Individual action items within the navigation bar
Let's examine the key props and features of each component.
BottomNavigation Props
The BottomNavigation component accepts several props to control its behavior and appearance:
Prop | Type | Default | Description |
---|---|---|---|
children | node | - | The content of the component, typically BottomNavigationAction items |
value | any | - | The value of the currently selected BottomNavigationAction |
onChange | function | - | Callback fired when the value changes |
showLabels | bool | false | If true, all labels will be displayed regardless of the selected state |
sx | object | - | The system prop that allows defining custom styles |
component | elementType | 'div' | The component used for the root node |
BottomNavigationAction Props
Each navigation item is represented by a BottomNavigationAction component with the following key props:
Prop | Type | Default | Description |
---|---|---|---|
icon | node | - | The icon element |
label | node | - | The label element |
value | any | - | The value of the BottomNavigationAction |
showLabel | bool | - | If true, the label will be visible. Defaults to the parent BottomNavigation's showLabels value |
sx | object | - | The system prop that allows defining custom styles |
disabled | bool | false | If true, the action will be disabled |
Controlled vs Uncontrolled Usage
Like many MUI components, BottomNavigation can be used in both controlled and uncontrolled modes:
-
Controlled: You manage the state externally and pass it to the component through the
value
prop, along with anonChange
handler to update the state when the user interacts with the component. -
Uncontrolled: The component manages its own state internally. This is simpler but gives you less control over the component's behavior.
For most applications, the controlled approach is recommended as it gives you more flexibility and allows you to integrate with routing libraries like React Router.
Getting Started: Basic Implementation
Let's start by creating a basic bottom navigation bar with MUI. First, we need to install the required packages.
Step 1: Install Dependencies
We'll need React, MUI core, and MUI icons:
npm install @mui/material @mui/icons-material @emotion/react @emotion/styled
Step 2: Create a Basic Bottom Navigation
Now, let's create a simple bottom navigation with four common tabs: Home, Search, Favorites, and Profile.
import React, { useState } from 'react';
import { BottomNavigation, BottomNavigationAction, Paper } from '@mui/material';
import { Home, Search, Favorite, Person } from '@mui/icons-material';
function BasicBottomNavigation() {
// State to track the currently selected tab
const [value, setValue] = useState(0);
// Handler for tab change
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<Paper
sx={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
zIndex: 1000, // Ensures the navigation stays on top of other content
}}
elevation={3}
>
<BottomNavigation
value={value}
onChange={handleChange}
showLabels // Always show labels for better usability
>
<BottomNavigationAction label="Home" icon={<Home />} />
<BottomNavigationAction label="Search" icon={<Search />} />
<BottomNavigationAction label="Favorites" icon={<Favorite />} />
<BottomNavigationAction label="Profile" icon={<Person />} />
</BottomNavigation>
</Paper>
);
}
export default BasicBottomNavigation;
In this basic example:
- We wrap the BottomNavigation in a Paper component to give it elevation and fix it to the bottom of the screen.
- We use useState to track the active tab index.
- We provide an onChange handler to update the state when a tab is selected.
- We set showLabels to true to always display the labels for better usability.
- Each BottomNavigationAction has an icon and a label.
This gives us a functional bottom navigation bar, but it doesn't actually navigate anywhere yet. Let's enhance it to work with React Router.
Integrating with React Router
To make our bottom navigation actually navigate between different views, we'll integrate it with React Router.
Step 3: Install React Router
npm install react-router-dom
Step 4: Create Page Components
Let's create simple page components for each tab:
// HomePage.jsx
import React from 'react';
import { Box, Typography } from '@mui/material';
function HomePage() {
return (
<Box sx={{ padding: 3, paddingBottom: 7 }}>
<Typography variant="h4">Home</Typography>
<Typography paragraph>
Welcome to the home page of our application!
</Typography>
</Box>
);
}
export default HomePage;
// SearchPage.jsx
import React from 'react';
import { Box, Typography, TextField, Button } from '@mui/material';
function SearchPage() {
return (
<Box sx={{ padding: 3, paddingBottom: 7 }}>
<Typography variant="h4">Search</Typography>
<Box sx={{ mt: 2, display: 'flex', gap: 1 }}>
<TextField fullWidth label="Search" variant="outlined" />
<Button variant="contained">Search</Button>
</Box>
</Box>
);
}
export default SearchPage;
// FavoritesPage.jsx
import React from 'react';
import { Box, Typography, List, ListItem, ListItemText } from '@mui/material';
function FavoritesPage() {
return (
<Box sx={{ padding: 3, paddingBottom: 7 }}>
<Typography variant="h4">Favorites</Typography>
<List>
<ListItem>
<ListItemText primary="Favorite Item 1" secondary="Description" />
</ListItem>
<ListItem>
<ListItemText primary="Favorite Item 2" secondary="Description" />
</ListItem>
<ListItem>
<ListItemText primary="Favorite Item 3" secondary="Description" />
</ListItem>
</List>
</Box>
);
}
export default FavoritesPage;
// ProfilePage.jsx
import React from 'react';
import { Box, Typography, Avatar, Paper } from '@mui/material';
function ProfilePage() {
return (
<Box sx={{ padding: 3, paddingBottom: 7 }}>
<Paper sx={{ p: 3, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<Avatar sx={{ width: 80, height: 80, mb: 2 }}>JD</Avatar>
<Typography variant="h5">John Doe</Typography>
<Typography color="textSecondary">john.doe@example.com</Typography>
<Typography paragraph sx={{ mt: 2 }}>
This is your profile page where you can view and edit your information.
</Typography>
</Paper>
</Box>
);
}
export default ProfilePage;
Step 5: Set Up Routing and Navigation
Now, let's integrate these components with React Router and our bottom navigation:
import React from 'react';
import { BrowserRouter, Routes, Route, useLocation, useNavigate } from 'react-router-dom';
import { BottomNavigation, BottomNavigationAction, Paper } from '@mui/material';
import { Home, Search, Favorite, Person } from '@mui/icons-material';
// Import our page components
import HomePage from './HomePage';
import SearchPage from './SearchPage';
import FavoritesPage from './FavoritesPage';
import ProfilePage from './ProfilePage';
// Navigation component that uses React Router
function AppNavigation() {
const location = useLocation();
const navigate = useNavigate();
// Map paths to indices for the BottomNavigation value
const pathToIndex = {
'/': 0,
'/search': 1,
'/favorites': 2,
'/profile': 3
};
// Get current value based on pathname
const value = pathToIndex[location.pathname] || 0;
// Handler for navigation changes
const handleChange = (event, newValue) => {
// Map indices back to paths
const paths = ['/', '/search', '/favorites', '/profile'];
navigate(paths[newValue]);
};
return (
<Paper
sx={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
zIndex: 1000
}}
elevation={3}
>
<BottomNavigation value={value} onChange={handleChange} showLabels>
<BottomNavigationAction label="Home" icon={<Home />} />
<BottomNavigationAction label="Search" icon={<Search />} />
<BottomNavigationAction label="Favorites" icon={<Favorite />} />
<BottomNavigationAction label="Profile" icon={<Person />} />
</BottomNavigation>
</Paper>
);
}
// Main App component with routes
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/search" element={<SearchPage />} />
<Route path="/favorites" element={<FavoritesPage />} />
<Route path="/profile" element={<ProfilePage />} />
</Routes>
<AppNavigation />
</BrowserRouter>
);
}
export default App;
In this implementation:
- We use React Router's
useLocation
hook to get the current path and map it to the corresponding tab index. - We use the
useNavigate
hook to programmatically navigate when a tab is clicked. - We've created a mapping between tab indices and routes to handle the bidirectional conversion.
- The bottom navigation is always visible, and the content changes based on the current route.
This approach gives us a fully functional mobile tab bar navigation that integrates with React Router for proper URL-based navigation.
Customizing the Bottom Navigation
Now that we have a working navigation, let's explore how to customize it to match your application's design.
Step 6: Styling with the sx Prop
MUI's sx
prop provides a powerful way to apply custom styles directly to components:
<BottomNavigation
value={value}
onChange={handleChange}
showLabels
sx={{
'& .MuiBottomNavigationAction-root': {
minWidth: 'auto',
padding: '6px 0',
color: 'text.secondary',
},
'& .Mui-selected': {
color: 'primary.main',
},
height: 60,
borderTop: '1px solid',
borderColor: 'divider',
}}
>
{/* BottomNavigationAction items */}
</BottomNavigation>
Step 7: Theming for Consistent Styling
For more systematic customization, you can use MUI's theming system to override the default styles for the BottomNavigation component:
import React from 'react';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
// Create a custom theme
const theme = createTheme({
components: {
MuiBottomNavigation: {
styleOverrides: {
root: {
backgroundColor: '#f8f9fa',
height: 65,
},
},
},
MuiBottomNavigationAction: {
styleOverrides: {
root: {
color: '#6c757d',
'&.Mui-selected': {
color: '#0d6efd',
},
},
label: {
fontSize: '0.75rem',
'&.Mui-selected': {
fontSize: '0.75rem',
},
},
},
},
},
});
function ThemedApp() {
return (
<ThemeProvider theme={theme}>
<BrowserRouter>
<App />
</BrowserRouter>
</ThemeProvider>
);
}
export default ThemedApp;
Step 8: Creating Custom Navigation Actions
For more complex navigation items, you might want to create custom actions:
import React from 'react';
import { BottomNavigation, BottomNavigationAction, Badge, Box } from '@mui/material';
import { Home, Search, Favorite, Person } from '@mui/icons-material';
function CustomBottomNavigation({ value, onChange }) {
return (
<BottomNavigation value={value} onChange={onChange} showLabels>
<BottomNavigationAction
label="Home"
icon={<Home />}
/>
<BottomNavigationAction
label="Search"
icon={<Search />}
/>
<BottomNavigationAction
label="Favorites"
icon={
<Badge badgeContent={3} color="error">
<Favorite />
</Badge>
}
/>
<BottomNavigationAction
label="Profile"
icon={
<Box sx={{ position: 'relative' }}>
<Person />
<Box
sx={{
position: 'absolute',
top: 0,
right: 0,
width: 8,
height: 8,
borderRadius: '50%',
backgroundColor: 'success.main',
}}
/>
</Box>
}
/>
</BottomNavigation>
);
}
export default CustomBottomNavigation;
In this example, we've added:
- A badge to the Favorites icon to show a count
- A custom online indicator for the Profile icon
Advanced Patterns
Let's explore some advanced patterns and techniques for working with the BottomNavigation component.
Step 9: Conditional Rendering of Navigation Items
Sometimes you might want to show or hide certain navigation items based on the user's authentication status or permissions:
import React, { useState, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { BottomNavigation, BottomNavigationAction, Paper } from '@mui/material';
import { Home, Search, Favorite, Person, Settings } from '@mui/icons-material';
function DynamicNavigation({ isAuthenticated, isAdmin }) {
const location = useLocation();
const navigate = useNavigate();
const [navigationItems, setNavigationItems] = useState([]);
const [value, setValue] = useState(0);
// Define navigation items based on user status
useEffect(() => {
const items = [
{ label: 'Home', icon: <Home />, path: '/' },
{ label: 'Search', icon: <Search />, path: '/search' },
];
if (isAuthenticated) {
items.push(
{ label: 'Favorites', icon: <Favorite />, path: '/favorites' },
{ label: 'Profile', icon: <Person />, path: '/profile' }
);
}
if (isAdmin) {
items.push({ label: 'Settings', icon: <Settings />, path: '/settings' });
}
setNavigationItems(items);
}, [isAuthenticated, isAdmin]);
// Update the selected value when the location changes
useEffect(() => {
const currentIndex = navigationItems.findIndex(item => item.path === location.pathname);
if (currentIndex !== -1) {
setValue(currentIndex);
}
}, [location.pathname, navigationItems]);
const handleChange = (event, newValue) => {
if (navigationItems[newValue]) {
navigate(navigationItems[newValue].path);
}
};
return (
<Paper sx={{ position: 'fixed', bottom: 0, left: 0, right: 0 }} elevation={3}>
<BottomNavigation value={value} onChange={handleChange} showLabels>
{navigationItems.map((item, index) => (
<BottomNavigationAction
key={item.path}
label={item.label}
icon={item.icon}
/>
))}
</BottomNavigation>
</Paper>
);
}
export default DynamicNavigation;
This approach dynamically adjusts the navigation items based on the user's authentication status and role.
Step 10: Adding Animation and Transitions
To make your navigation more engaging, you can add animations and transitions:
import React from 'react';
import { BottomNavigation, BottomNavigationAction, Paper, Zoom } from '@mui/material';
import { Home, Search, Favorite, Person } from '@mui/icons-material';
import { styled } from '@mui/material/styles';
// Create a styled version of BottomNavigationAction with custom animations
const AnimatedNavigationAction = styled(BottomNavigationAction)(({ theme }) => ({
transition: 'transform 0.2s ease-in-out',
'&.Mui-selected': {
transform: 'translateY(-4px)',
},
'& .MuiBottomNavigationAction-label': {
transition: 'opacity 0.3s, transform 0.3s',
opacity: 0,
transform: 'translateY(4px)',
},
'&.Mui-selected .MuiBottomNavigationAction-label': {
opacity: 1,
transform: 'translateY(0)',
},
}));
function AnimatedNavigation({ value, onChange }) {
return (
<Paper sx={{ position: 'fixed', bottom: 0, left: 0, right: 0 }} elevation={3}>
<BottomNavigation
value={value}
onChange={onChange}
sx={{ height: 65 }}
>
<AnimatedNavigationAction
label="Home"
icon={
<Zoom in={value === 0 ? false : true}>
<Home />
</Zoom>
}
/>
<AnimatedNavigationAction
label="Search"
icon={
<Zoom in={value === 1 ? false : true}>
<Search />
</Zoom>
}
/>
<AnimatedNavigationAction
label="Favorites"
icon={
<Zoom in={value === 2 ? false : true}>
<Favorite />
</Zoom>
}
/>
<AnimatedNavigationAction
label="Profile"
icon={
<Zoom in={value === 3 ? false : true}>
<Person />
</Zoom>
}
/>
</BottomNavigation>
</Paper>
);
}
export default AnimatedNavigation;
This implementation:
- Uses styled components to add animations to the navigation actions
- Applies a small "lift" effect to the selected tab
- Animates the labels to fade in and slide up when selected
- Uses MUI's Zoom transition for the icons
Step 11: Creating a Floating Action Button in the Center
A common pattern in mobile UIs is to have a prominent floating action button in the center of the bottom navigation:
import React from 'react';
import { BottomNavigation, BottomNavigationAction, Paper, Fab } from '@mui/material';
import { Home, Search, Favorite, Person, Add } from '@mui/icons-material';
import { styled } from '@mui/material/styles';
// Create a styled component for the container
const NavigationContainer = styled(Paper)(({ theme }) => ({
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
zIndex: 1000,
}));
// Create a styled component for the FAB
const CenterFab = styled(Fab)(({ theme }) => ({
position: 'absolute',
top: -30,
left: '50%',
transform: 'translateX(-50%)',
zIndex: 1001,
}));
function NavigationWithFAB({ value, onChange, onFabClick }) {
return (
<NavigationContainer elevation={3}>
<CenterFab color="primary" aria-label="add" onClick={onFabClick}>
<Add />
</CenterFab>
<BottomNavigation
value={value}
onChange={onChange}
showLabels
sx={{
height: 60,
justifyContent: 'space-around',
'& .MuiBottomNavigationAction-root': {
maxWidth: 'none',
minWidth: 'auto',
padding: '6px 0',
},
}}
>
<BottomNavigationAction label="Home" icon={<Home />} />
<BottomNavigationAction label="Search" icon={<Search />} />
<BottomNavigationAction label="" disabled sx={{ opacity: 0 }} />
<BottomNavigationAction label="Favorites" icon={<Favorite />} />
<BottomNavigationAction label="Profile" icon={<Person />} />
</BottomNavigation>
</NavigationContainer>
);
}
export default NavigationWithFAB;
In this implementation:
- We create a floating action button positioned in the center of the navigation bar
- We add a disabled placeholder navigation action to create space for the FAB
- We customize the layout to ensure even spacing of the navigation items
Performance Optimization
Let's look at some techniques to optimize the performance of your bottom navigation.
Step 12: Memoizing Components
To prevent unnecessary re-renders, we can use React's memoization:
import React, { memo, useCallback } from 'react';
import { BottomNavigation, BottomNavigationAction, Paper } from '@mui/material';
import { Home, Search, Favorite, Person } from '@mui/icons-material';
// Memoized icon components
const HomeIcon = memo(() => <Home />);
const SearchIcon = memo(() => <Search />);
const FavoriteIcon = memo(() => <Favorite />);
const ProfileIcon = memo(() => <Person />);
function OptimizedNavigation({ value, onChange }) {
// Memoize the onChange handler to prevent unnecessary re-renders
const handleChange = useCallback((event, newValue) => {
onChange(newValue);
}, [onChange]);
return (
<Paper sx={{ position: 'fixed', bottom: 0, left: 0, right: 0 }} elevation={3}>
<BottomNavigation
value={value}
onChange={handleChange}
showLabels
>
<BottomNavigationAction label="Home" icon={<HomeIcon />} />
<BottomNavigationAction label="Search" icon={<SearchIcon />} />
<BottomNavigationAction label="Favorites" icon={<FavoriteIcon />} />
<BottomNavigationAction label="Profile" icon={<ProfileIcon />} />
</BottomNavigation>
</Paper>
);
}
export default memo(OptimizedNavigation);
Step 13: Lazy Loading Routes
For larger applications, you can improve initial load time by lazy loading the page components:
import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { CircularProgress, Box } from '@mui/material';
import AppNavigation from './AppNavigation';
// Lazy load page components
const HomePage = lazy(() => import('./HomePage'));
const SearchPage = lazy(() => import('./SearchPage'));
const FavoritesPage = lazy(() => import('./FavoritesPage'));
const ProfilePage = lazy(() => import('./ProfilePage'));
// Loading fallback
const LoadingFallback = () => (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
<CircularProgress />
</Box>
);
function App() {
return (
<BrowserRouter>
<Suspense fallback={<LoadingFallback />}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/search" element={<SearchPage />} />
<Route path="/favorites" element={<FavoritesPage />} />
<Route path="/profile" element={<ProfilePage />} />
</Routes>
</Suspense>
<AppNavigation />
</BrowserRouter>
);
}
export default App;
Accessibility Enhancements
Ensuring your navigation is accessible is crucial for all users.
Step 14: Improving Keyboard Navigation and Screen Reader Support
import React from 'react';
import { BottomNavigation, BottomNavigationAction, Paper, useMediaQuery } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { Home, Search, Favorite, Person } from '@mui/icons-material';
function AccessibleNavigation({ value, onChange }) {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
// For desktop, we might want to use a different navigation pattern
if (!isMobile) {
return null;
}
return (
<Paper
sx={{ position: 'fixed', bottom: 0, left: 0, right: 0 }}
elevation={3}
component="nav"
aria-label="Main Navigation"
>
<BottomNavigation
value={value}
onChange={onChange}
showLabels
>
<BottomNavigationAction
label="Home"
icon={<Home />}
aria-label="Home Page"
component="button"
/>
<BottomNavigationAction
label="Search"
icon={<Search />}
aria-label="Search Page"
component="button"
/>
<BottomNavigationAction
label="Favorites"
icon={<Favorite />}
aria-label="Favorites Page"
component="button"
/>
<BottomNavigationAction
label="Profile"
icon={<Person />}
aria-label="Profile Page"
component="button"
/>
</BottomNavigation>
</Paper>
);
}
export default AccessibleNavigation;
In this implementation:
- We add proper ARIA labels to improve screen reader support
- We use the
component
prop to ensure proper keyboard navigation - We use a media query to only show the bottom navigation on mobile devices
Best Practices and Common Issues
When implementing a bottom navigation with MUI, keep these best practices in mind:
Content Padding
Ensure your page content has enough bottom padding to prevent it from being hidden behind the navigation bar:
// In your page components
<Box sx={{
padding: 3,
paddingBottom: 10 // Extra padding at the bottom
}}>
{/* Page content */}
</Box>
Responsive Behavior
Consider hiding the bottom navigation on larger screens and showing a different navigation pattern:
import React from 'react';
import { useMediaQuery, useTheme } from '@mui/material';
import MobileNavigation from './MobileNavigation';
import DesktopNavigation from './DesktopNavigation';
function ResponsiveNavigation(props) {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
return isMobile ? <MobileNavigation {...props} /> : <DesktopNavigation {...props} />;
}
export default ResponsiveNavigation;
Deep Linking and Back Navigation
When users navigate to your app via a deep link, ensure the correct tab is selected:
import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function AppWithDeepLinking() {
const location = useLocation();
useEffect(() => {
// Extract the base path to determine which tab should be active
const basePath = '/' + (location.pathname.split('/')[1] || '');
// Map paths to tab indices
const pathToIndex = {
'/': 0,
'/search': 1,
'/favorites': 2,
'/profile': 3
};
// Set the active tab based on the current path
const activeIndex = pathToIndex[basePath] || 0;
setActiveTab(activeIndex);
}, [location.pathname]);
// Rest of your component
}
Common Issues and Solutions
Issue: Bottom navigation covers important content
Solution: Add consistent padding to the bottom of all page content:
// Create a layout component
import React from 'react';
import { Box } from '@mui/material';
const BOTTOM_NAV_HEIGHT = 60;
function PageLayout({ children }) {
return (
<Box sx={{
pb: `${BOTTOM_NAV_HEIGHT + 16}px`, // Bottom padding to account for navigation
minHeight: `calc(100vh - ${BOTTOM_NAV_HEIGHT}px)`, // Ensure pages fill the viewport
}}>
{children}
</Box>
);
}
export default PageLayout;
Issue: Selected tab doesn't match the current page after using browser back button
Solution: Listen for history changes and update the selected tab:
import React, { useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
function NavigationWithHistorySupport() {
const location = useLocation();
const navigate = useNavigate();
const [value, setValue] = useState(0);
// Update value when location changes (including back/forward navigation)
useEffect(() => {
const pathToIndex = {
'/': 0,
'/search': 1,
'/favorites': 2,
'/profile': 3
};
setValue(pathToIndex[location.pathname] || 0);
}, [location]);
const handleChange = (event, newValue) => {
const paths = ['/', '/search', '/favorites', '/profile'];
navigate(paths[newValue]);
};
// Rest of your component
}
Issue: Navigation feels slow or janky
Solution: Optimize rendering performance:
import React, { useMemo } from 'react';
import { BottomNavigation, BottomNavigationAction } from '@mui/material';
import { Home, Search, Favorite, Person } from '@mui/icons-material';
function PerformantNavigation({ value, onChange }) {
// Memoize the navigation items to prevent unnecessary re-renders
const navigationItems = useMemo(() => [
{ label: 'Home', icon: <Home />, path: '/' },
{ label: 'Search', icon: <Search />, path: '/search' },
{ label: 'Favorites', icon: <Favorite />, path: '/favorites' },
{ label: 'Profile', icon: <Person />, path: '/profile' }
], []);
return (
<BottomNavigation value={value} onChange={onChange} showLabels>
{navigationItems.map((item, index) => (
<BottomNavigationAction
key={item.path}
label={item.label}
icon={item.icon}
/>
))}
</BottomNavigation>
);
}
export default React.memo(PerformantNavigation);
Complete Example: A Full-Featured Mobile Navigation
Let's put everything together into a comprehensive example that incorporates all the best practices:
import React, { useState, useEffect, useMemo } from 'react';
import {
BrowserRouter,
Routes,
Route,
useLocation,
useNavigate,
Outlet
} from 'react-router-dom';
import {
BottomNavigation,
BottomNavigationAction,
Paper,
Badge,
useMediaQuery,
Box,
CircularProgress,
Slide
} from '@mui/material';
import { useTheme } from '@mui/material/styles';
import {
Home,
Search,
Favorite,
Person,
Notifications
} from '@mui/icons-material';
// Lazy-loaded page components
const HomePage = React.lazy(() => import('./pages/HomePage'));
const SearchPage = React.lazy(() => import('./pages/SearchPage'));
const FavoritesPage = React.lazy(() => import('./pages/FavoritesPage'));
const ProfilePage = React.lazy(() => import('./pages/ProfilePage'));
const NotificationsPage = React.lazy(() => import('./pages/NotificationsPage'));
// Layout component with bottom navigation
function AppLayout() {
const location = useLocation();
const navigate = useNavigate();
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const [value, setValue] = useState(0);
const [notificationCount, setNotificationCount] = useState(3);
// Navigation items with badges
const navigationItems = useMemo(() => [
{
label: 'Home',
icon: <Home />,
path: '/'
},
{
label: 'Search',
icon: <Search />,
path: '/search'
},
{
label: 'Favorites',
icon: <Favorite />,
path: '/favorites'
},
{
label: 'Notifications',
icon: (
<Badge badgeContent={notificationCount} color="error" max={99}>
<Notifications />
</Badge>
),
path: '/notifications'
},
{
label: 'Profile',
icon: <Person />,
path: '/profile'
}
], [notificationCount]);
// Update selected tab when location changes
useEffect(() => {
const currentIndex = navigationItems.findIndex(item =>
location.pathname === item.path || location.pathname.startsWith(`${item.path}/`)
);
if (currentIndex !== -1) {
setValue(currentIndex);
}
}, [location.pathname, navigationItems]);
// Handler for tab selection
const handleChange = (event, newValue) => {
navigate(navigationItems[newValue].path);
// Clear notifications when navigating to notifications page
if (navigationItems[newValue].path === '/notifications') {
setNotificationCount(0);
}
};
return (
<Box sx={{ pb: isMobile ? '56px' : 0 }}>
{/* Main content area */}
<Box sx={{
minHeight: '100vh',
pb: isMobile ? 8 : 0
}}>
<React.Suspense fallback={
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
<CircularProgress />
</Box>
}>
<Outlet />
</React.Suspense>
</Box>
{/* Bottom navigation - only on mobile */}
{isMobile && (
<Slide direction="up" in={true} mountOnEnter unmountOnExit>
<Paper
sx={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
zIndex: 1000
}}
elevation={3}
component="nav"
aria-label="Main Navigation"
>
<BottomNavigation
value={value}
onChange={handleChange}
showLabels
sx={{
height: 56,
'& .MuiBottomNavigationAction-root': {
minWidth: 'auto',
padding: '6px 0',
color: 'text.secondary',
},
'& .Mui-selected': {
color: 'primary.main',
},
}}
>
{navigationItems.map((item, index) => (
<BottomNavigationAction
key={item.path}
label={item.label}
icon={item.icon}
aria-label={`${item.label} Page`}
/>
))}
</BottomNavigation>
</Paper>
</Slide>
)}
</Box>
);
}
// Main App component
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<AppLayout />}>
<Route index element={<HomePage />} />
<Route path="search" element={<SearchPage />} />
<Route path="favorites" element={<FavoritesPage />} />
<Route path="notifications" element={<NotificationsPage />} />
<Route path="profile" element={<ProfilePage />} />
<Route path="*" element={<HomePage />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;
This comprehensive example includes:
- Responsive design that only shows the bottom navigation on mobile devices
- Integration with React Router for proper navigation
- Lazy loading of page components for better performance
- Badge notifications with automatic clearing
- Proper accessibility attributes
- Animation for a smoother user experience
- Loading indicators during page transitions
- Consistent padding to prevent content from being hidden
- Performance optimizations with useMemo
- Proper handling of deep linking and browser history
Wrapping Up
MUI's BottomNavigation component provides a powerful and flexible way to implement mobile tab bar navigation in your React applications. By following the patterns and practices outlined in this guide, you can create a navigation experience that is intuitive, responsive, accessible, and performant.
Remember that bottom navigation works best when it provides access to the top-level destinations in your application. Keep the number of tabs between 3-5 for the best user experience, and ensure that each tab represents a distinct and important section of your app.
With the right implementation, your bottom navigation can significantly improve the usability of your mobile web application, making it feel more like a native app and providing users with a familiar and comfortable way to navigate between different sections.