loading...
Cover image for Smarter Tooltips and Popovers with Popper 2

Smarter Tooltips and Popovers with Popper 2

fezvrasta profile image Federico Zivolo ・7 min read

Four years ago I released the first version of a library with the goal to solve a problem that is as simple as it is complex: positioning poppers. These are tooltips, popovers, drop-downs, and more—elements that "pop out" from the flow of the document and get overlaid on top of the existing UI, positioned next to a reference element, such as a button.

Poppers are part of the everyday web; we see them in all the websites and applications we use daily to help the UI make better use of space. If all controls were immediately visible to the user, we'd have some pretty messy interfaces. But what hides behind these little widgets?

Tooltip positioned on the right of an SVG popcorn box, where it has been prevented from overflowing its container to stay visible for the user

One of the most complex parts that compose them is the positioning logic, the code that makes the popper float near the element that triggered it. How many times have we seen them partially positioned outside the page, getting stuck on scroll, not responding when the page is resized, with their content cut out? I can count a few. Probably fewer these days, likely in part because of libraries like Popper fixing the issue, but in the past, it was far more common.

Popper focuses on this part of the problem. It’s a reliable, lightweight, and extensible positioning engine library to make sure your tooltips and popovers are positioned optimally.

Adoption

The library experienced a huge adoption! In numbers:

  • Over 650,000 GitHub repositories depend on it
  • Over 3,000,000 npm downloads per week
  • Over 1,000,000,000 lifetime CDN hits

npm download stats. From 50,000 per day in January 2018 to over 600,000 per day in December 2019

Big projects picked it as their library of choice for their tooltip and popover needs. In fact, it's very likely you've used Popper either as a developer or a user on the web at some point in time in the past few years, even if you didn't know it existed.

The most popular UI libraries Bootstrap, Foundation, and Material UI all decided to use it to help their users create poppers that didn't have UX problems. Then there are the CMS', such as Drupal, and Joomla!, who decided to adopt it, along with big companies such as Microsoft with WebClipper and Fluent UI, Atlassian with its AtlasKit, GitLab, and more.

So what was the problem?

The problem was solved and everybody was happy; so what was left to do, right? Well, not quite.

The API wasn’t the friendliest ever made, the auto-generated documentation probably led a lot of people to change career, and fixing bugs was a feat only the mightiest of the heroes could undertake. After all, the code base was 4 years old, written by a 4 years younger and less experienced me.

No software lasts forever, so in early 2018, a year after the v1.0 release, I headed to Google to learn how to create a clean branch with Git, and after a couple of days a new Git branch was ready to host the future of Popper!

18 months later...

Despite initial development going strong, life got busy and the rewrite laid dormant for a long time. After all, Popper 1 still worked fine, and had growing adoption.

It was now mid-2019, over a year since I had worked on the next Popper version. v1 saw larger and larger adoption, but more and more bugs and API flaws began to surface. Millions of people were using and depending on the library, but it had problems appearing from every line of code. I felt guilty; I couldn't leave Popper in this state, I had to fix it.

In June 2019, I submitted a pull request asking for help because I was stuck on a key part of the next version of the library—the logic to detect the overflow of the popper relative to a given boundary. No one could offer help.

Things weren't looking good for Popper 2. Its development had completely stopped in its tracks. Will Popper 1 really be the only version of a tooltip and popover positioning engine the web—its developers and users—have to experience? Buggy and not very lightweight?

Finally, one day, 5 months after the pull request opened without any progress, I finally thought of a solution. Just like that, the development of Popper 2 resumed. You can view the pull request here.

@atomiks joined as a member, and we started collaborating to finally release Popper 2. I would like to give a big shout-out to him as he helped develop many of the modifiers (features) to bring Popper 2 on par with v1, but without the bugs 😅.

Development in numbers

Are you hungry for numbers? Over the 2 months of resumed development on Popper 2:

  • 🐞 38 bugs fixed
  • ↩️ 70+ pull requests made
  • 🎁 20 next versions released
  • ✏️ 600+ commits pushed
  • ⚡ 2x faster updates
  • 🔽 70% smaller base library size

We made improvements

So, what changed? Everything! The new codebase is a complete rewrite. It's written in a more modular way, it’s type checked by Flow with TypeScript definitions automatically generated, the modifiers API is more powerful, and the best of all, the library is lighter, tree-shakable, and faster!

Let’s go through the two key changes made to the API.

1. We replaced the class with a function:

// Old - bye classes!
const popperInstance = new Popper(reference, popper);

// New - hello closures!
const popperInstance = createPopper(reference, popper);

2. Modifiers are an array of objects with a phase and requires property that order modifiers based on their dependencies:

const offsetModifier = {
  name: 'offset',
  enabled: true,
  // core phases: read, main, write
  phase: 'main',
  // dependencies
  requires: ['popperOffsets'],
  // core logic
  fn({state}) {}
};

createPopper(reference,  popper, {
  modifiers: [offsetModifier],
});

The order property therefore no longer needs to be tinkered with.

Thank you Stack Overflow user Nikhil Aggarwal for the algorithm behind the ordering of modifiers. He didn't know what it was for of course, but we used it in this library, a year and a half later! You can view its adaption here.

Putting Popper on a diet

While Popper 1 gained more features and became more reliable, its size had grown with it. From a small 3kB library, it had become a not indifferent 7kB one! A lot of our users asked how it was possible for a “simple tooltip library” to be so heavy. While this question doesn’t take in consideration the incredible amount of logic and edge cases the library needed to support, the question was still valid.

To reduce the library size, we adopted two different approaches: the code has been completely rewritten, and it has been made modular.

Deleting legacy hacks

Rewriting the whole library was a necessary effort to get rid of a lot of legacy code and hacks that had been introduced over the years—especially for legacy browsers (yes, we're looking at you IE10). A lot of positioning logic was simply flawed or approached from the wrong angle.

An extensible core

The new approach was to write code that made sense and didn’t need adjustments down the road. This may seem obvious, but when working with the DOM APIs of our beloved browsers, all kind of nightmares come visit you while you code. And the most effective way to address them in a bloated code base was to add logic to handle them, rather than rewrite the whole code chunk. This alone helped us to reduce the library size by approximately 35%, but it wasn’t enough! So we made Popper modular.

The way the new version of the library is designed of a core, which includes the logic needed to run the Popper lifecycle, and runs the most basic logic needed to enable the modifiers to work. On top of it, we have a set of modifiers (or plugins) that add functionalities to Popper, but are completely optional. This means you can now import Popper, and have it do just a very basic positioning calculation, while leaving out all the logic needed by overflow detection, event listeners, flip behavior, custom offsets support, etc… Thanks to this change, the core library is now 2kB small! While the version with the most common modifiers is just 3kB.

We fixed the documentation

The Popper v1 documentation was notoriously known for being extremely difficult to read and understand.

A YouTube user saying "This library has one of the worst documentations I have ever had the displeasure to read." with 18 likes

The documentation is now completely rewritten using Gatsby and MDX, and hopefully it’s going to be way easier to understand. We also added a tutorial with a full, complete, reproducible example to help beginners as much as possible. You can find it at https://popper.js.org/docs/v2/tutorial/.

We're sorry for everyone who had to endure the v1 documentation! 🙈

We embraced our love of popcorn and amusement parks

We fully embraced the name "Popper" and made the website popcorn-themed, because well, who doesn't love popcorn?

Visit the website here: https://popper.js.org.

If you enjoy and work with React/Gatsby, feel free to contribute to the website to make it even more amusement park themed and fun! The GitHub repository is completely open-source, available here: https://github.com/popperjs/popper-core.

Wrapping up

It’s been quite a journey, but I’m so excited to release Popper 2 to you all today! Feel free to leave feedback for us. We'd love to hear your thoughts.

Discussion

pic
Editor guide
 

Hi, thank you for your amazing work!

I'm curious if Popper is a good fit for my project. I need a library that will position elements directly over other elements -- not beside them -- to create the illusion of an inline edit area.

Do you think Popper would be a good fit for this or not?

 

Take a look at the offset modifier, it can apply negative offsets to make the popper overlap with its reference element

 

So, the best way to get a popover to appear directly above an element would be to use the offset option as a function, as seen in the docs and use the reference element's dimensions to calculate the offset?

 

Congratulations! :)

For those projects that depend on Popper 1, it'd be super valuable to have explicit "Upgrading from Popper 1 to Popper 2" docs. AFAICT that doesn't exist yet (I looked at github.com/popperjs/popper-core/re..., popper.js.org/docs/v2/tutorial/ and in this blog post). It could make an enormous difference in terms of Popper 2 adoption: without clear docs that cover both the most commonly encountered backwards compatibility breaks that need to be overcome and warn about the harder edge cases, many projects will probably stick with Popper 1. This would increase the maintenance burden too.

In any case: congratulations, awesome work, and thank you for this very valuable contribution! We're looking forward to hopefully updating Drupal from Popper 1 to 2 soon :)

 

Thanks! I plan to write a note to help the upgrade process, there aren’t a lot of breaking changes luckily.

 

Huge thanks from creating Popper! It has been tremendous help whenever I have needed to develop some type of a pop element like a tooltip or a menu.

The documentation certainly has changed to be a lot more understandable and it looks super beautiful.

 

Thanks for the popcorn! 🍿

 

You're doing an amazing job Federico! Thanks for Popper!

 

Thanks so much for doing all the heavy lifting, Federico!

 

Thank you for sticking with the project and all of the contributions! People are mean, but you’ve got a great attitude of staying positive.

We can’t wait to make the upgrade at Pingboard!

 

Congrats on the release! 🎉

 

Super cool, looking forward to using the new version!