How to Perfectly Position and Style a Material-UI Popover

The Material-UI Popover is a handy component for giving quick information to users. However, it has characteristics of both tooltip and modal components, so it has some quirks.

In this example, we’ll position the popover relative to it’s anchor element and also relative to the click position in the window. Furthermore, we’ll transform the starting position of the popover. Last, we’ll have a bit of styling plus understanding the DOM and why clicks behave as they do when popover is open.

Notice the toggle buttons at the bottom.

Link to Code Sandbox is in Resources section.

If you want a video based course for learning Material-UI, Implement High Fidelity Designs with Material-UI and ReactJS on Udemy is an incredible resource. It has 1400+ reviews, averages 4.6 stars, and has a 30 Day Money Back Guarantee.

The course was recently updated and covers a huge amount of MUI components and designs. At 40+ hours of content and 243 lectures across 16 sections, I believe it lays an excellent foundation for using Material-UI. Udemy runs lots of sales, check the price below.

This image has an empty alt attribute; its file name is image-7.png

Popover Positioning

There are four positioning props that I examine and use in this demo:

  • anchorReference
  • anchorPosition/anchorOrigin (depends on anchorReference)
  • transformOrigin

Clicking using anchorOrigin (and passing the inner div as anchorEl):

And clicking the same spot using anchorPosition:

Before we discuss these, I need to briefly discuss the components on this page. Just for the sake of example, I used some math to calculate where the popover should show with anchorPosition and anchorEL.

I set the outside div to margin: 100px, padding: 80px, and width/height 300px. Normally you should code using flex or some other responsive option, but I wanted to show precisely what happens with the different popover props.

I will use the margin, padding, and width/height in my calculations for determining if Popover should close on click and where to position the Popover.

Let’s discuss each prop and then take a look at my code.

anchorReference: 'anchorPosition' simply makes the Popover take it’s positioning coordinates from the anchorPosition prop. anchorReference: 'anchorEl' likewise only makes the component take position from the anchorEl and anchorOrigin props, which work as a pair. The anchorReference does nothing else.

The anchorPosition prop takes a left and top numeric value, which are the coordinates the Popover will appear on the screen. In my code, I take the x , y coordinates of a click and pass them to the anchorPosition prop.

The anchorEl prop is optional and take a component or element. If no element is passed, it will default to the topmost parent element of the Popover (probably not what you want). The anchorOrigin prop typically takes two strings, a string for horizontal positioning (‘left’, ‘center’, ‘right’) and a string for vertical positioning (‘top’, ‘center’, ‘bottom’). However, it can also take numeric horizontal and vertical values. 0, 0 positioning with anchorOrigin will set the Popover at the top left corner of the anchorEl element.

The transformOrigin prop will flip which side of the Popover is adjacent to the anchorEl or anchorPosition coordinates.

<Popover
  className={classes.popover}
  //style={{ zIndex: -1 }}
  open={open}
  anchorReference={anchorReference}
  anchorEl={anchorEl}
  anchorPosition={{ top: coordinates[1], left: coordinates[0] }}
  anchorOrigin={{
    vertical:
      (coordinates[1] - (margin + padding)) / side <= 0.5
      ? "top"
      : "bottom",
    horizontal:
      (coordinates[0] - (margin + padding)) / side <= 0.5
      ? "left"
      : "right"
  }}
  transformOrigin={{
    horizontal: transform
  }}
  onClose={handleClose}
>

In my Popover demo, I have anchorEl, anchorPosition, and anchorOrigin props populated. When anchorReference is toggled via button click, the component consumes the appropriate props and ignores props it doesn’t need.

As previously mentioned, anchorPosition takes the click coordinates. anchorOrigin does more than that: my code essentially figures out which quadrant of the div was clicked and translates that into ‘left’, ‘right’, ‘top’, and ‘bottom’ values.

Now let’s take a look at the click handler React code:

const handleClick = (event) => {
    //manual calculation of div side + outer div padding + outer div margin
    //not recommended in real life :)
  if (
    event.pageX >= padding + margin &&
    event.pageX <= side + padding + margin &&
    event.pageY >= padding + margin &&
    event.pageY <= side + padding + margin
  ) {
    setCoordinates([event.pageX, event.pageY]);
    setAnchorEl(event.currentTarget);
    setOpen(true);
  } else {
    setOpen(false);
  }
};

If the click coordinates are outside of the div (a very manual calculation), I close the popup. Otherwise, I update the props.

Popover DOM and Click Swallowing

You may be wondering why I don’t just use the event.currentTarget value in the code above instead of manually calculating whether the div was clicked. The Popover component actually is built on the modal component. This means there is actually an invisible modal on the screen when Popover is open. Furthermore, the Popover actually seems to duplicate the click handler (and some click event attributes) from the anchorEl.

All this means that when Popover is open and a user clicks again anywhere, they are actually clicking the Popover modal. This leads to the difficulty with doing anything more than closing the Popover on second click, and the difficulty with updating the props like I did.

For a visual, look at the image of dev tools below.

The divs and buttons are children of the #root element. However, the Popover (once opened) is actually a sibling of the #root div and covers it with a higher z-index. This shows how it swallows clicks. The Popover is actually the component getting the click, but the ‘mask’ is translucent and so the user doesn’t know they are clicking the Popover.

Popover Styling

Let’s take a look at the z-index of the Popover. MUI is actually applying the z-index as an inline styling to make it harder to override. That means you must override it inline (if you wanted to). I set it to -1 just to see the effect:

It set the Popover behind all other components and made the buttons clickable again. However, it also made the text and background look problematic.

Probably you won’t need to adjust the z-index, unless you needed to set it higher for some reason. However, you may want to style other aspect of the Popover. If so, the following selector should do the trick:

popover: {
  "&.MuiPopover-root": {
    //whatever you want
  }
}

Make sure to add the popover class to the Popover component. This syntax (JSS) is then saying, “select all elements with class popover and class MuiPopover-root”.

You might also have a component inside of the Popover, like I have Paper in my demo. This can also take a class and be styled. In fact, it is more likely that you will be able to accomplish your styling needs by targeting this component instead of the Popover. However, the above code hopefully gave you the flexibility to accomplish whatever you may need.

Resources

Think you’re a JavaScript expert? Test yourself on these 50 difficult JavaScript questions.

Code Sandbox:

Link to MUI Docs

Share this post:

Leave a Comment

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