DIY React Popups with Popper.js 2

Ismayil Khayredinov
ITNEXT
Published in
3 min readJan 24, 2020

--

I needed to build a custom select input in React. As usual, it came down to rendering a popup element with available options and figuring out its positioning relative to the input element, making sure the component is responsive and reacts properly to window resizing, while maintaining keyboard accessibility. There is no contender when it comes DOM positioning libraries — Popper.js is the industry standard— yet Popper.js 2 is relatively new and as of recent only available as a release candidate. I thought I would give it a go — from what I could gather, aside from performance improvements, the main difference is consistency in how “modifiers” are implemented and triggered.

Component Skeleton

Let’s setup our basic component. I am going to use class-based React components, as they are easier to reason about, but like all other components, these can be refactored into functional components using React hooks.

In our basic infrastructure, we have 2 elements: a trigger and a popup. Let’s render them into the DOM assigning them corresponding references. Because we will be manipulating DOM elements, we want to be able to access these 2 element easily via their references.

Popper.js Library

Let’s pull in Popper.js:

npm install @popperjs/core

We can now use Popper.js constructor to wire up our trigger/popup relationships:

We have set up two lifecycle hooks: on componentDidMountwe wire up Popper.js by feeding it both trigger and popup references (as well as any modifiers we may need, i.e. via props). IncomponentWitllUnmount, we want to make sure we clean up any bindings by destroying the popper instance. Additionally, we want to make sure our popup reference is present in the DOM at all times, even if the actual popup content are not shown — we can then control popper positioning by forcing update whenever popup contents become visible.

Portal the popup

To avoid dealing with z-index madness, we can attach our popup to the body of the DOM document, thus making sure it’s not conflicting with any of the elements adjacent to our trigger element. This is not 100% necessary, and you can figure these things out via CSS, but if you already have react-dom installed in your project, you might as well take advantage of it.

Trap the focus

Focus trapping is an important accessibility tool. In case your popup contains inputs or other focusable elements (you may want to allow yours users to navigate through the available select or menu options using the keyboard). focus-trap-react is a fantastic library that you can pull into your project to avoid dealing with a number of accessibility issues:

  • exit/close your popup when user clicks outside
  • exit/close your popup when user presses ESC
  • trap the focus inside the popup element to ensure smooth keyboard experience

That’s it. You now have a popup element that you have full control of. You can create dropdown menus, select inputs, tooltips and whatever else you please that requires relative positioning to the trigger element.

Bonus: Using references on div elements gives you access to all the DOM elements properties, including clientWidth, thus you can use style prop to set the width of your popup element to match the width of your reference element:

<div style={{ width: this.toggleRef.current.clientWidth }} />

--

--

Full-stack developer, passionate about front-end frameworks, design systems and UX.