How to Position an MUI Drawer Under AppBar (Responsive Sizing!)

The Material-UI Drawer component is an essential layout component for everything from navigation links to informational sidebars.

In this React MUI Drawer example, we will make a mobile responsive Drawer component that is always visible on screen sizes > 375px, and on smaller screens it opens and closes with the click of a menu icon.

Material-UI Drawer styling

Additionally, we will discuss the following features for Material-UI Drawer:

  • fitting Drawer under the Appbar
  • removing Backdrop when using the Drawer
  • using Paper as an internal component

This post has both MUI v5 and v4 code. A live Code Sandbox is in the Resources section.

I recorded a live coding session of placing the Drawer under the AppBar, you can watch it here.

Material-UI Drawer Under AppBar
Material-UI Drawer Under AppBar

MUI Mobile Responsive Drawer

Material-UI responsive Drawer
Drawer is hidden by default at < 375px

The Drawer acts as a sidebar when it is on the side, contains a menu, and does not have a backdrop. There are three primary considerations with the design of the mobile responsive Drawer in this demo:

  • Showing/hiding the Drawer component
  • Showing/hiding the Menu Icon
  • Full/reduced width of the main content section

The menu icon is sometimes referred to as a ‘Hamburger Menu’, even though the import name is MenuIcon.

We’re going to get deep into the JSX and hooks:

export default function ClippedDrawer() {
  const classes = useStyles();
  const greaterThan375 = useMediaQuery("(min-width:375px)");
  const [open, setOpen] = React.useState(greaterThan375);

  useEffect(() => {
    setOpen(greaterThan375);
  }, [greaterThan375]);

  const handleMenuClick = () => {
    setOpen(!open);
  };

  return (
    <div className={classes.root}>
      <AppBar position="fixed" className={classes.appBar}>
        <Toolbar>
          <IconButton //hide on desktop
            color="inherit"
            onClick={handleMenuClick}
            edge="start"
            className={clsx(classes.menuButton, greaterThan375 && classes.hide)}
          >
            <MenuIcon />
          </IconButton>
          <Typography variant="h6" noWrap>
            Responsive Drawer
          </Typography>
        </Toolbar>
      </AppBar>
      <Drawer
        className={classes.drawer}
        variant="persistent"
        //elevation={3} only works with variant="temporary"
        open={open}
        transitionDuration={{
          enter: transitionDuration,
          exit: transitionDuration
        }}
        classes={{
          paper: classes.drawerPaper
        }}
        PaperProps={{ elevation: 9 }}
      >
        <Toolbar />
        <div className={classes.drawerContainer}>
          <List>
            {["Home", "Page 1", "Page 2", "Page 3"].map((text, index) => (
              <ListItem button key={text}>
                <ListItemIcon>
                  <AppsIcon />
                </ListItemIcon>
                <ListItemText primary={text} />
              </ListItem>
            ))}
          </List>
        </div>
      </Drawer>
      <main
        className={clsx(classes.content, {
          [classes.contentShift]: open
        })}
      >
        <Toolbar />
        <Typography>
          Resize the screen above/below 375px to see responsiveness
        </Typography>
      </main>
    </div>
  );
}

The most important code for the Drawer itself to be responsive is the Boolean greaterThan375 created with useMediaQuery. This hook updates the Boolean dynamically. greaterThan375 can then be used in the useEffect hook to open or close the Drawer by updating an open Boolean.

The menu icon gets an extra class applied when greaterThan375 is true. The class simply flips the icon to display: "none“. As info, clsx simply allows conditional logic to be passed to the className prop.

The main content requires a bit more fancy css. My code is actually very similar to the example in the MUI docs, which I forked for my Code Sandbox.

To give the effect of expanding or contracting content, margin is transitioned in and out when the open Boolean is toggled. Instead of using a constant, another option is to use the theme.transitions.duration object, which has some values for transition durations. However, I also used the transitionDuration constant to control the Drawer component transition, so I used it for main content margin as well.

const transitionDuration = 1000; //can also use theme.transitions.duration

content: {
  flexGrow: 1,
  padding: theme.spacing(3),
  transition: theme.transitions.create("margin", {
    easing: theme.transitions.easing.sharp,
    duration: transitionDuration
  }),
  marginLeft: -drawerWidth
},
contentShift: {
  transition: theme.transitions.create("margin", {
    easing: theme.transitions.easing.easeOut,
    duration: transitionDuration
  }),
  marginLeft: 0
}

MUI Drawer Styling

Here’s a complete post on MUI Drawer background color, text color, width, height, and elevation. It uses the MUI sx prop instead of makeStyles. The sx prop is recommended over the makeStyles hook by the MUI team.

If you are using the “persistent” or “permanent” variants, you will need to pass any shadow (also known as elevation) to the inner Paper component using this prop: PaperProps={{ elevation: 9 }}. The “temporary” variant can actually directly receive elevation directly: elevation={9}. Elevation, which is another name for box shadow, ranges from 1 to 16. The higher the number, the more box-shadow is applied (more grey and ‘elevated’ look).

Nesting MUI Drawer Under the Appbar and Removing Backdrop

The Appbar is a common component to have across the top of the screen. It comes with some shadow underneath it and should appear to be ‘over’ the Drawer. We need two things to accomplish this:

  • A <Toolbar> that acts as a gutter above the content of the Drawer
  • A higher z-index on the Appbar than the Drawer

These are both standard in the examples in the Material-UI docs. However, it is useful to explain them.

First, the Appbar cleverly can be guaranteed to be above the Drawer with a simple bit of code:

appBar: {
  zIndex: theme.zIndex.drawer + 1
}

Second, the <Toolbar> works as a gutter because of the css applied in the DOM. See below:

Material-UI Toolbar DOM

If you are using the ‘temporary’ Drawer variant and you want to remove the Backdrop, it’s possible with CSS. Add the following to our existing class:

drawer: {
  "& .MuiBackdrop-root": {
    display: "none"
  }
}

An alternative solution may be to use the ModalProps, but I prefer the above approach.

Here’s how to position the Drawer inside a container.

MUI V5 Drawer Example Code

Below is the updated code for sizing and positioning the Drawer in MUI V5. I chose to use the new v5 hook for makeStyles because it plays nicely with the main and div elements that I used. If you do not have React components like divs, then you can use v5 MUI sx prop or styled components.

import React, { useEffect } from "react";
import { makeStyles } from "@mui/styles";
import clsx from "clsx";
import Drawer from "@mui/material/Drawer";
import AppBar from "@mui/material/AppBar";
import Toolbar from "@mui/material/Toolbar";
import List from "@mui/material/List";
import Typography from "@mui/material/Typography";
import ListItem from "@mui/material/ListItem";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import AppsIcon from "@mui/icons-material/Apps";
import IconButton from "@mui/material/IconButton";
import MenuIcon from "@mui/icons-material/Menu";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from '@mui/material/styles';

const drawerWidth = 240;
const transitionDuration = 1000; //can also use theme.transitions.duration

const useStyles = makeStyles(() => {
  return ({
  menuButton: {
    marginRight: (theme) => theme.spacing(2)
  },
  hide: {
    display: "none"
  },
  appBar: {
    zIndex: (theme) => theme.zIndex.drawer + 1
  },
  drawer: {
    width: (theme) => theme.drawerWidth,
    "& .MuiBackdrop-root": {
      display: "none"
    }
  },
  drawerPaper: {
    width: (theme) => theme.drawerWidth,
    backgroundColor: "rgba(120, 120, 120, 0.2)"
  },
  content: {
    padding: (theme) => theme.spacing(3),
    transition: (theme) => theme.transitions.create("margin", {
      easing: theme.transitions.easing.easeOut,
      duration: transitionDuration
    }),
    minWidth: (theme) => theme.drawerWidth,
    marginLeft: (theme) => 0
  },
  contentShift: {
    transition: (theme) => theme.transitions.create("margin", {
      easing: theme.transitions.easing.easeOut,
      duration: transitionDuration
    }),
    minWidth: (theme) => theme.drawerWidth,
    marginLeft: (theme) => theme.drawerWidth
  }
})});

export default function ClippedDrawer() {
  const theme = useTheme();
  const greaterThan375 = useMediaQuery("(min-width:376px)");
  theme.drawerWidth = greaterThan375 ? drawerWidth : '100%';
  const classes = useStyles(theme);
  const [open, setOpen] = React.useState(greaterThan375);

  useEffect(() => {
    setOpen(greaterThan375);
  }, [greaterThan375]);

  const handleMenuClick = () => {
    setOpen(!open);
  };

  return (
    <div>
      {/*fixed is default */}
      <AppBar position="fixed" className={classes.appBar}>
        <Toolbar>
          <IconButton //hide on desktop
            color="inherit"
            onClick={handleMenuClick}
            edge="start"
            className={clsx(classes.menuButton, greaterThan375 && classes.hide)}
          >
            <MenuIcon />
          </IconButton>
          <Typography variant="h6" noWrap>
            Responsive Drawer
          </Typography>
        </Toolbar>
      </AppBar>
      <Drawer
      //add full width for responsive
        className={classes.drawer}
        variant="temporary"
        //elevation={3} only works with variant="temporary"
        open={open}
        transitionDuration={{
          enter: transitionDuration,
          exit: transitionDuration
        }}
        classes={{
          paper: classes.drawerPaper
        }}
        PaperProps={{ elevation: 9 }}
      >
        <Toolbar/>
        <div>
          <List>
            {["Home", "Page 1", "Page 2", "Page 3"].map((text, index) => (
              <ListItem button key={text}>
                <ListItemIcon>
                  <AppsIcon />
                </ListItemIcon>
                <ListItemText primary={text} />
              </ListItem>
            ))}
          </List>
        </div>
      </Drawer>
      <main
        className={clsx(classes.content, {[classes.contentShift]: open})}
      >
      <Toolbar/>
        <Typography>
          Resize the screen above/below 375px to see responsiveness
        </Typography>
      </main>
    </div>
  );
}

Resources

My MUI course on Udemy is now available!!! Build a full MUI app from beginning to end, learn every aspect of the sx prop, styled API, and the theme, and tackle the most challenging components! Do you want an active Q&A with me?!? Check here for coupons for my MUI course on Udemy.

Link to Code Sandbox with full React code.

See this article for an example of React-Router Links inside the Drawer component.

Mui Drawer Docs

Share this post:

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.