Creating a Navigation Bar with MUI AppBar Component in Next.js

Creating a Navigation Bar with MUI AppBar Component in Next.js

The App Bar displays information and actions relating to the current screen. The top App bar provides content and actions related to the current screen. It's used for branding, screen titles, navigation, and actions.

It can transform into a contextual action bar or be used as a navbar.

In this blog post, we will be creating a Navigation Bar for with MUI AppBar Component.

Create a PrimarySearchAppBar.tsx File

MUI provides a wide range of UI components to make web development faster and more efficient. Among these components is the MUI PrimarySearchAppBar, which is a customizable and easy-to-use component that can be integrated into any React application.

Create a components folder inside src folder, then create a file named PrimarySearchAppBar.tsx

import * as React from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';

export default function PrimarySearchAppBar() {

    return (
        <Box sx={{ flexGrow: 1 }}>
            <AppBar position="fixed">
                <Toolbar></Toolbar>
            </AppBar>
        </Box>
    );
}

This is a functional component that defines a PrimarySearchAppBar.

It uses Material UI's Box, AppBar, Container, and Toolbar components to create a fixed-position app bar with a container.

The sx prop on the Box component allows for inline styling using Material UI's theme. In this case, the flexGrow: 1 property ensures that the app bar will take up all available space in its container.

The position="fixed" prop on the AppBar component makes the app bar stay fixed at the top of the screen even if the user scrolls down the page.

The maxWidth="xl" prop on the Container component sets the maximum width of the container to xl, which is one of the predefined values in Material UI. This means that the container will be as wide as the screen, up to a maximum width of xl.

The disableGutters prop on the Toolbar component removes the default left and right padding of the toolbar, allowing you to have full control over the content inside.

This code does not have any content in Toolbar yet, but you can add buttons, icons, or other custom components to the Toolbar component as children.

The export default statement at the end of the component means that this component can be imported into other files in your project. It can be imported into index.tsx like this:

import Head from 'next/head'
import Box from '@mui/material/Box'
import PrimarySearchAppBar from '@/components/PrimarySearchAppBar'

export default function Home() {
  return (
    <>
      <Head>
        <title>Employee Leave Manager App</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <Box>
        <PrimarySearchAppBar />
      </Box>
    </>
  )
}

output:

Add a brand name to Toolbar

The Typography component will be used to create a text element with the following properties:

  • variant="h6": sets the text to use the "h6" heading variant

  • noWrap: prevents the text from wrapping onto a new line if it exceeds the element's width

  • component="a": sets the HTML tag of the rendered element to an a anchor tag

  • href="/": sets the URL that the anchor tag should link to as the root directory of the website

  • sx: an object containing custom styling properties for the element using the Material-UI styling system

Within the sx object, the following styles would be applied:

  • mr: 2: adds a margin of 2 units (specified elsewhere) to the left and right of the element

  • display: { md: 'flex' }: sets the element to use flexbox display when the viewport width is at least medium-sized

  • fontFamily: 'monospace': sets the font family of the text to a monospace font

  • fontWeight: 700: sets the font weight to a bold value of 700

  • letterSpacing: '.3rem': sets the amount of space between each character to be a fixed value of 0.3 rem

  • color: 'inherit': sets the color of the text to inherit its parent's color property

  • textDecoration: 'none': removes any underlining or other decorations on the anchor text.

<Typography
    variant="h6"
    noWrap
    component="a"
    href="/"
    sx={{
        mx: 2,
        display: { md: 'flex' },
        fontFamily: 'monospace',
        fontWeight: 700,
        letterSpacing: '.3rem',
        color: 'inherit',
        textDecoration: 'none',
    }}
>
    ELM
</Typography>

Overall, this code creates a stylized link or button that reads "ELM" and links to the root directory of the website. It uses a monospace font, bold text, and capital letters with letter-spacing for emphasis. The element is also set to use a flexbox display on medium-sized screens.

output:

Adding Email and Notifications IconButtons to Toolbar

We will render two icon buttons on a Toolbar. The first button displays an email icon and the second button displays a notifications icon.

<Box sx={{ ml: 2>
    <IconButton size="large" aria-label="mails" color="inherit">
        <Badge badgeContent={4} color="error">
            <MailIcon />
        </Badge>
    </IconButton>
    <IconButton
        size="large"
        aria-label="notifications"
        color="inherit">
        <Badge badgeContent={17} color="error">
            <NotificationsIcon />
        </Badge>
    </IconButton>
</Box>

The Box component is used to wrap the two IconButton components and applies some styling using the sx prop. In this case, it sets the left margin to 2.

The IconButton component creates a clickable icon button that can be used as a part of a user interface. The size prop sets the size of the button to "large", aria-label prop sets an accessible label for the button, and the color prop sets the color of the icon to "inherit" which allows the button to inherit its parent's color.

output:

Adding Avatar IconButton to Toolbar

Avatars are found throughout material design with uses in everything from tables to dialog menus and AppBars. We will add Avatar to AppBar as:

<IconButton
    size="large"
    edge="end"
    aria-label="account of current user"
    aria-haspopup="true"
    color="inherit"
    sx={{ mr: 2 }}>
    <Avatar sx={{ width: 32, height: 32 }}>A</Avatar>
</IconButton>

This code creates an IconButton component with props that specify its size, edge, color, and accessibility properties. The IconButton component is used to render an interactive button element that displays an Avatar component inside it.

The Avatar component is used to render a circular icon or image representing the current user's account. It has props that specify its size and styling. In this case, the Avatar is styled with a width and height of 32 pixels and displays the letter "A".

The sx prop is used to apply custom styles to the IconButton and Avatar components using the Material UI styling system. The mr property sets a margin-right of 2 in the IconButton component, pushing any elements to the right of it away from it.

output:

Adding Dropdown Menu to Avatar

A menu displays a list of choices on a temporary surface. It appears when the user interacts with a button or other control.

In over case we are going to add Menu to Avatar IconButton as:

const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const isMenuOpen = Boolean(anchorEl);

const handleProfileMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
    setAnchorEl(event.currentTarget);
};

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

const menuId = '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}>
            <ListItemIcon>
                <AccountCircle fontSize="small" />
            </ListItemIcon>
            Profile
        </MenuItem>
        <Divider />
        <MenuItem onClick={handleMenuClose}>
            <ListItemIcon>
                <Settings fontSize="small" />
            </ListItemIcon>
            Settings
        </MenuItem>
        <MenuItem onClick={handleMenuClose}>
            <ListItemIcon>
                <Logout fontSize="small" />
            </ListItemIcon>
            Logout
        </MenuItem>
    </Menu>
);

The code generates a menu with three options: Profile, Settings, and Logout. The menu is triggered by clicking on Avatar IconButton on the AppBar and it opens anchored to the element.

The component has four states defined using the useState hook: anchorEl, isMenuOpen, handleProfileMenuOpen, and handleMenuClose. anchorEl is the element on the page that the menu is anchored to, isMenuOpen is a boolean value that tracks whether the menu is open or not, handleProfileMenuOpen is a function that sets the anchorEl state to the current target element when the menu is opened, and handleMenuClose is a function that sets the anchorEl state to null when the menu is closed.

The menuId variable is used to set the ID of the menu element, and renderMenu is a JSX expression that renders a Menu component with the following props:

  • anchorEl: The element on the page that the menu is anchored to.

  • anchorOrigin: The position on the page where the menu is anchored. In this case, it is anchored to the top-right corner of the element.

  • id: The ID of the menu element.

  • keepMounted: A boolean value that keeps the menu mounted even when it is hidden. This can improve performance in some cases.

  • transformOrigin: The position on the menu where it is anchored. In this case, it is anchored to the top-right corner of the menu.

  • open: A boolean value that determines whether the menu is open or not. It is set to the value of isMenuOpen.

  • onClose: A function that is called when the menu is closed. It calls the handleMenuClose function.

The renderMenu variable is returned by the component, so when it is rendered on the page below the AppBar component

<AppBar position="fixed">
    ...
</AppBar>
{ renderMenu }

It generates a menu element that can be opened and closed by clicking on the triggering element the Avatar IconButton. We have to add

  • aria-controls={menuId}

  • onClick={handleProfileMenuOpen}

to Avatar IconButton.

When an option in the menu is clicked, the handleMenuClose function is called to close the menu.

<IconButton
    size="large"
    edge="end"
    aria-label="account of current user"
    aria-controls={menuId}
    aria-haspopup="true"
    onClick={handleProfileMenuOpen}
    color="inherit"
    sx={{ mr: 2 }}>
    <Avatar sx={{ width: 32, height: 32 }}>A</Avatar>
</IconButton>

output:

Making AppBar menus responsive

To make AppBar Menus render well on a variety of devices and window or screen sizes from minimum to maximum display size, we will make it responsive to ensure usability and satisfaction. To do so add the following code:

const [mobileMoreAnchorEl, setMobileMoreAnchorEl] =
    React.useState<null | HTMLElement>(null);
const isMobileMenuOpen = Boolean(mobileMoreAnchorEl);

const handleMobileMenuClose = () => {
    setMobileMoreAnchorEl(null);
};

const handleMobileMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
    setMobileMoreAnchorEl(event.currentTarget);
};

const mobileMenuId = '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="new mails" color="inherit">
                <MailIcon />
            </IconButton>
            <p>Messages</p>
        </MenuItem>
        <MenuItem>
            <IconButton
                size="large"
                aria-label="new notifications"
                color="inherit">
                <NotificationsIcon />
            </IconButton>
            <p>Notifications</p>
        </MenuItem>
    </Menu>
);

Next, make changes to handleMenuClose:

const handleMenuClose = () => {
    setAnchorEl(null);
    handleMobileMenuClose();
};

Next, add one more IconButton to AppBar and add renderMobileMenu as shown:

<AppBar position="fixed">
    <Toolbar disableGutters>
        ...
        <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>
{ renderMenu }
{ renderMobileMenu }

The Box component is used to wrap the IconButton component and it applies custom styles to the component using the Material-UI styling system. The sx prop is used to specify the styles. The display property of sx specifies that on screens with a width of xs (extra small), the box should be displayed as a flex container, and on screens with a width of md (medium) or larger, the box should be hidden (none).

output:

And

Creating SearchInput.tsx

The SearchInput component can be used as a reusable UI element in any React application that requires a search input field with a search icon. It provides a well-styled and functional search input field that can be easily integrated into AppBar.

The SearchInput component to allows users to search for Employees based on their names.

import InputBase from '@mui/material/InputBase';
import { alpha, styled } from '@mui/material/styles';
import React, { } from 'react';
import SearchIcon from '@mui/icons-material/Search';

const Search = styled('div')(({ theme }) => ({
    position: 'relative',
    borderRadius: theme.shape.borderRadius,
    backgroundColor: alpha(theme.palette.common.white, 0.15),
    '&:hover': {
        backgroundColor: alpha(theme.palette.common.white, 0.25),
    },
    marginRight: theme.spacing(2),
    marginLeft: 4,
}));

const SearchIconWrapper = styled('div')(({ theme }) => ({
    padding: theme.spacing(0, 2),
    height: '100%',
    position: 'absolute',
    pointerEvents: 'none',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
}));

const StyledInputBase = styled(InputBase)(({ theme }) => ({
    color: 'inherit',
    '& .MuiInputBase-input': {
        padding: theme.spacing(1, 1, 1, 0),
        // vertical padding + font size from searchIcon
        paddingLeft: `calc(1em + ${theme.spacing(4)})`,
        transition: theme.transitions.create('width'),
    },
}));

const SearchInput = () => {

    return (
        <Search>
            <SearchIconWrapper>
                <SearchIcon />
            </SearchIconWrapper>
            <StyledInputBase
                placeholder="Search…"
                inputProps={{ 'aria-label': 'search' }} />
        </Search>
    );
}

export default SearchInput;

This is a React component that renders a search input field with a search icon beside it. The styles for this component are defined using the styled-components library.

The component is divided into three styled-components:

  • Search: This is a div element that represents the search bar container. It has a white background with 15% opacity, rounded borders and a darker background (25% opacity) on hover.

  • SearchIconWrapper: This is a div element that contains the search icon (<SearchIcon />). It is positioned absolutely within the search container and centered using flexbox.

  • StyledInputBase: This is an InputBase component from the Material UI library that represents the search input field. It inherits its color from the parent element, and has some padding and transition effects to provide a smooth experience for the user.

Finally, the SearchInput component is defined by rendering these three styled components inside the Search container. The search input field has a placeholder text of "Search..." .

Adding SearchInput to Toolbar in AppBar

To show SearchInput in Toolbar we only have to add it as:

<AppBar position="fixed">
    <Toolbar disableGutters>
        ...
        <Box sx={{ flexGrow: 1 }} />
        <SearchInput />
        ...
    </Toolbar>
</AppBar>

output:

Here's a required implementation of PrimarySearchAppBar.tsx:

import * as React from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import MailIcon from '@mui/icons-material/Mail';
import NotificationsIcon from '@mui/icons-material/Notifications';
import Avatar from '@mui/material/Avatar';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import ListItemIcon from '@mui/material/ListItemIcon';
import Divider from '@mui/material/Divider';
import AccountCircle from '@mui/icons-material/AccountCircle';
import Settings from '@mui/icons-material/Settings';
import Logout from '@mui/icons-material/Logout';
import MoreIcon from '@mui/icons-material/MoreVert';
import SearchInput from './SearchInput';


export default function PrimarySearchAppBar() {
    const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
    const isMenuOpen = Boolean(anchorEl);

    const [mobileMoreAnchorEl, setMobileMoreAnchorEl] =
        React.useState<null | HTMLElement>(null);
    const isMobileMenuOpen = Boolean(mobileMoreAnchorEl);

    const handleProfileMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
        setAnchorEl(event.currentTarget);
    };

    const handleMobileMenuClose = () => {
        setMobileMoreAnchorEl(null);
    };

    const handleMenuClose = () => {
        setAnchorEl(null);
        handleMobileMenuClose();
    };

    const handleMobileMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
        setMobileMoreAnchorEl(event.currentTarget);
    };

    const menuId = '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}>
                <ListItemIcon>
                    <AccountCircle fontSize="small" />
                </ListItemIcon>
                Profile
            </MenuItem>
            <Divider />
            <MenuItem onClick={handleMenuClose}>
                <ListItemIcon>
                    <Settings fontSize="small" />
                </ListItemIcon>
                Settings
            </MenuItem>
            <MenuItem onClick={handleMenuClose}>
                <ListItemIcon>
                    <Logout fontSize="small" />
                </ListItemIcon>
                Logout
            </MenuItem>
        </Menu>
    );

    const mobileMenuId = '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="new mails" color="inherit">
                    <MailIcon />
                </IconButton>
                <p>Messages</p>
            </MenuItem>
            <MenuItem>
                <IconButton
                    size="large"
                    aria-label="new notifications"
                    color="inherit">
                    <NotificationsIcon />
                </IconButton>
                <p>Notifications</p>
            </MenuItem>
        </Menu>
    );

    return (
        <Box sx={{ flexGrow: 1 }}>
            <AppBar position="fixed">
                <Toolbar disableGutters>
                    <Typography
                        variant="h6"
                        noWrap
                        component="a"
                        href="/"
                        sx={{
                            mx: 2,
                            display: { md: 'flex' },
                            fontFamily: 'monospace',
                            fontWeight: 700,
                            letterSpacing: '.3rem',
                            color: 'inherit',
                            textDecoration: 'none',
                        }}
                    >
                        ELM
                    </Typography>
                    <Box sx={{ flexGrow: 1 }} />
                    <SearchInput />
                    <Box sx={{ ml: 2, display: { xs: 'none', md: 'flex' } }}>
                        <IconButton size="large" aria-label="mails" color="inherit">
                            <MailIcon />
                        </IconButton>
                        <IconButton
                            size="large"
                            aria-label="notifications"
                            color="inherit"
                        >
                            <NotificationsIcon />
                        </IconButton>
                    </Box>
                    <IconButton
                        size="large"
                        edge="end"
                        aria-label="account of current user"
                        aria-controls={menuId}
                        aria-haspopup="true"
                        onClick={handleProfileMenuOpen}
                        color="inherit"
                        sx={{ mr: 1 }}>
                        <Avatar sx={{ width: 32, height: 32 }}>A</Avatar>
                    </IconButton>
                    <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>
            {renderMenu}
            {renderMobileMenu}
        </Box>
    );
}

Conclusion

This blog post demonstrates how to create a responsive AppBar that includes a Notification icon, Email icon, and Avatar icon. Additionally, we added a SearchInput to the AppBar to enable users to search for content within the application. The PrimarySearchInput component is optimized for responsiveness across all devices.