DEV Community

Cover image for 5 best popover libraries for React
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

5 best popover libraries for React

Written by Ibadehin Mojeed✏️

Popovers are essential and commonly used UI elements that reveal additional information when a user interacts with a designated element. Examples of professional use cases for popovers include dropdown navigation menus, date pickers, and guided tours during onboarding processes.

Within the React ecosystem, many libraries cater to this need, offering varying levels of customization, position strategies, and responsiveness. In this comprehensive guide, we’ll explore five standout popover and positioning libraries for React:

  1. Popper.js
  2. react-tiny-popover
  3. react-laag
  4. reactjs-popup
  5. React Joyride

For each of these libraries, we will delve into their setup, positioning capabilities, responsiveness, and more, using the same basic demo project to see each library in action. You can check out the code and demos on CodeSandbox or jump to the end to see a comparison table for all five libraries.

Before we dive into the libraries, let’s first discuss why you should consider using popover libraries rather than building your popovers from scratch.

Why popover libraries?

Creating a robust popover widget from scratch can be complex for many reasons. For example, it’s crucial to ensure that you’re handling numerous edge cases thoroughly. These complexities can pose significant challenges to your development process.

Let's take a closer look at a simple, reusable popover component created from scratch.

Imagine a dashboard page where users can choose between Admin and User roles for themselves. When users click on the button to set their role, a popover will appear, presenting the available roles.

Achieving this in React involves creating a component state to toggle the popover content and applying a position: absolute on the popover to align it with the reference element. The following code should render a customized popover:

const TableRow = ({ ... }) => {
  const [isPopoverOpen, setIsPopoverOpen] = useState(false);

  return (
    // ...
    <Popover
      isOpen={isPopoverOpen}
      content={<div className="popover-content">{popup}</div>}
    >
      <div
        className="btn-container"
        onClick={() => setIsPopoverOpen(!isPopoverOpen)}
      >
        <Button label="Admin" />
        <RxCaretSort />
      </div>
    </Popover>
    // ...
  );
};
Enter fullscreen mode Exit fullscreen mode

The actual Popover component will look like so:

const Popover = ({ isOpen, content, children }) => {
  return (
    <div className="no-library">
      {children}
      {isOpen && content}
    </div>
  ); 
};

export default Popover;
Enter fullscreen mode Exit fullscreen mode

The GIF below demonstrates the outcome: Demo Of Popover Built From Scratch In React Showing Two Dummy Users With Option To Set Role As Admin Or User. Mouse Shown Clicking On Each Button To Open Popover To Set Role See the code on CodeSandbox. While this demo works fine at the moment, there are two significant problems that can occur — placement and clipping — each with many potential edge cases to address. Let's explore these now.

The placement problem

Currently, the reference element — represented by the button in the GIF above — is centrally positioned, so the popover works properly. However, when the element is near the right edge of the browser, it may lead to overflow and necessitate scrolling.

To prevent this overflow, you can make some manual adjustments. While manual adjustment is fine when implementing the popover in a single location, for a reusable component, we must incorporate dynamic positioning logic.

This placement problem becomes particularly important when we need to account for diverse screen sizes and layouts. Unfortunately, addressing all of those potential display scenarios can delay the development process significantly.

Clipping issues

It's unpredictable where a reusable popover component might end up. It could be situated within a container with styles that could disrupt the popup, like overflow: hidden or other styles.

In such a scenario, if the element is near the bottom edge of the boundary, the popover will be truncated: Example Of The Clipping Issue Presented By Popovers Built From Scratch In React. Popover Shown Getting Cut Off For Bottom User We can enable the popover to appear outside and disregard the overflow: hidden of the parent element by utilizing React Portals:

import { createPortal } from "react-dom";

const Popover = ({ isOpen, content, children }) => {
  return (
    <div>
      {children}
      {isOpen && createPortal(content, document.body)}
    </div>
  );
};

export default Popover;
Enter fullscreen mode Exit fullscreen mode

The createPortal function allows us to render the content directly into a specified DOM node — in this case, within the <body>.

Using a portal ensures that the popover content escapes the constraints of table overflow styling. However, it comes with the drawback that the content loses its original positioning: Using React Portals On Popovers Built From Scratch To Address Clipping Issue From Previous Image. Popover Appears In Bottom Left Corner Of Image Rather Than Directly Below Dropdown Instead of investing development hours in addressing edge cases, let's explore standout libraries that help tackle these issues in an elegant and performant manner.

1. Popper.js: Popover and tooltip positioning engine

Popper.js is a positioning library designed for elements that "pop," including popovers.

Technically, Popper.js is not a popover library in itself — we still have to create the popover as demonstrated earlier. However, it significantly simplifies the process of constructing a robust popover by handling complexities related to placement, overflow, and flipping.

Due to its capabilities, various UI libraries such as Material UI, Foundation, and Bootstrap incorporate Popper.js to ensure precise positioning of popup elements. Similarly, React developers frequently rely on it as a foundational tool for creating custom popovers.

While not exclusive to React, Popper.js provides a react-popper wrapper, making it easier to incorporate Popper's powerful positioning capabilities into React applications.

Now, let's explore how the Popper library can assist in directly attaching the popover to the reference button.

Setting up Popper and using it with Portals

First, let’s install @popperjs/core and its React wrapper:

npm i react-popper @popperjs/core
Enter fullscreen mode Exit fullscreen mode

In the earlier example, we observed how the createPortal function renders the popover content in a different part of the DOM. Now, let's employ the Popper library to precisely address the positioning.

The react-popper wrapper provides a usePopper Hook that requires three arguments: a referenceElement, popperElement, and a configuration object. In this configuration, we can specify the initial placement of the popover:

import { useState } from "react";

import { usePopper } from "react-popper";
// ...
const TableRow = ({ name, email, popup }) => {
  // ...
  const [referenceElement, setReferenceElement] = useState(null);
  const [popperElement, setPopperElement] = useState(null);

  const { styles, attributes } = usePopper(referenceElement, popperElement, {
    placement: "bottom-start",
    modifiers: [{ name: "offset", options: { offset: [0, 5] } }]
  });

  return (
    // ...
    <Popover
      isOpen={isPopoverOpen}
      content={
        <div
          ref={setPopperElement}
          style={styles.popper}
          {...attributes.popper}
          className="popover-content"
        >
          {popup}
        </div>
      }
    >
      <div
        className="btn-container"
        ref={setReferenceElement}
        onClick={() => setIsPopoverOpen(!isPopoverOpen)}
      >
        <Button label="Admin" />
        <RxCaretSort />
      </div>
    </Popover>
    // ...
  );
};

export default TableRow;
Enter fullscreen mode Exit fullscreen mode

The usePopper Hook returns an object containing styles and attributes necessary for positioning the popover along with the reference element. To enable proper positioning, we assigned the popper and reference setters to the ref attributes for both the popover content and the reference element.

With this implementation, the popover should now seamlessly attach to the button, escaping any overflow hidden in the parent container and achieving the correct positioning. The result should look like so: Popover Demo Created Using Popper Js Library For Similar Result To First Image See the project demo and its source code on CodeSandbox.

Using react-popper without createPortal

Let's explore how the Popper library manages positioning without utilizing the createPortal function. After removing it from the code, we now have the following:

// import { createPortal } from "react-dom";

const Popover = ({ isOpen, content, children }) => {
  return (
    <div>
      {children}
      {isOpen && content}
      {/* {isOpen && createPortal(content, document.body)} */}
    </div>
  );
};

export default Popover;
Enter fullscreen mode Exit fullscreen mode

The library is intelligent enough to flip the popover content to a different placement that fits better, thanks to its ability to handle offsetParent contexts. There's no need to move the popover outside of its original DOM context: Demo Of Using Popper Js Library Without React Portals See the source code on CodeSandbox and check out the live demo to see for yourself.

Popper.js is an excellent choice if you want to create your own custom popover, as it‘s capable of managing complex, dynamic positioning logic effortlessly. However, if you're seeking a ready-made popover component with comprehensive features, let's explore the next library.

2. react-tiny-popover: A lightweight but brilliant library

If you're in search of simplicity and a minimal footprint, react-tiny-popover stands out as an excellent choice for constructing popovers.

Despite its lightweight nature, this library is robust and provides great customization options. Moreover, it features a responsive design by default, ensuring compatibility across various screen sizes.

Setting up and using react-tiny-popover

You can install react-tiny-popover as-is with no other dependencies:

npm install react-tiny-popover –save
Enter fullscreen mode Exit fullscreen mode

Using this library is straightforward. Instead of creating a custom popover component, this library provides us with one that we can directly render:

// ...
import { Popover, ArrowContainer } from "react-tiny-popover";

const TableRow = ({ name, email, popup }) => {
  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
  return (
    // ...
    <Popover
      isOpen={isPopoverOpen}
      positions={["top", "bottom", "left", "right"]}
      align="start"
      padding={8}
      onClickOutside={() => setIsPopoverOpen(false)}
      content={({ position, childRect, popoverRect }) => (
        <ArrowContainer
          position={position}
          childRect={childRect}
          popoverRect={popoverRect}
          arrowColor={"#2a2e2d"}
          arrowSize={8}
        >
          <div>
            <div className="popover-content">{popup}</div>
          </div>
        </ArrowContainer>
      )}
    >
      <div
        onClick={() => setIsPopoverOpen(!isPopoverOpen)}
        className="btn-container"
      >
        <Button label="Admin" />
        <RxCaretSort />
      </div>
    </Popover>
    // ...
  );
};
Enter fullscreen mode Exit fullscreen mode

The Popover component from the library requires three props:

  • isOpen to track visibility
  • children, which represents the reference element in this case
  • content, which takes the content that will appear as the popover

The expected behavior of the Popover component is as follows: Popover Demo Created Using React Tiny Popover Library Similar to the positioning capability offered by Popper.js, react-tiny-popover can also guard against container boundaries and reposition itself to prevent hidden overflow. However, unlike Popper, this library by default moves the popover content outside of its DOM context, appending it to the document.body: Browser Dev Tools Open To Show Popover Content Outside Dom Context And Appended To Document Body, Which React Tiny Popover Library Does By Default To position the popover adjacent to the button that activates it, include a parentElement prop, and then wrap the Popover with a reference element:

import { useState, useRef } from "react";
// ...
const TableRow = ({ name, email, popup }) => {
  // ...
  const boxContainerRef = useRef();
  return (
    // ...
      <div className="popover-container" ref={boxContainerRef}>
        <Popover
          // ...
          parentElement={boxContainerRef.current}
        >
          {...}
        </Popover>
    </div>
    //   ...
  );
};
Enter fullscreen mode Exit fullscreen mode

The popover should now be attached to the container element instead of the body element: Browser Dev Tools Open To Show Popover Now Attached To Container Element Instead Of Body Element If you prefer a popover without an arrow, you can modify the content prop to exclude the ArrowContainer like so:

content={<div className="popover-content">{popup}</div>}
Enter fullscreen mode Exit fullscreen mode

See the source code and demo on CodeSandbox.

3. react-laag

react-laag provides a Hook for positioning popovers. Similar to Popper.js, there's still some effort involved in creating the visual aspects of the popover. The library takes care of intricate calculations to achieve precise positioning.

Its popularity has been growing recently, going from less than 50 thousand weekly npm downloads to over 138 thousand over the past year: Growing Popularity Of React Laag Library Shown By Number Of Npm Downloads

Setting up and using react-laag

Like react-tiny-popover, you can install react-laag with zero dependencies:

npm install react-laag
Enter fullscreen mode Exit fullscreen mode

Here is a quick example of integrating the library into our project:

// ...
import { useLayer, Arrow } from "react-laag";

const TableRow = ({ name, email, popup }) => {
  const [isOpen, setIsOpen] = useState(false);

  const { renderLayer, triggerProps, layerProps, arrowProps } = useLayer({
    isOpen,
    onOutsideClick: () => setIsOpen(false),
    onDisappear: () => setIsOpen(false),
    overflowContainer: false,
    auto: true,
    placement: "right-start",
    triggerOffset: 12,
    arrowOffset: 16
  });

  return (
    // ...
    <Popover
      isOpen={isOpen}
      content={renderLayer(
        <div className="popover-content" {...layerProps}>
          {popup}
          <Arrow {...arrowProps} backgroundColor="#2a2e2d" />
        </div>
      )}
    >
      <div
        {...triggerProps}
        onClick={() => setIsOpen(!isOpen)}
        className="btn-container"
      >
        <Button label="Admin" />
        <RxCaretSort />
      </div>
    </Popover>
    // ...
  );
};
Enter fullscreen mode Exit fullscreen mode

Despite its primary focus on positioning, this library provides essential popover features — such as the onOutsideClick function — to manage the outside click event that closes the popover. Refer to the expected behavior in the demo below: Demo Of Popover Created With React Laag Library See this react-laag demo’s source code on CodeSandbox.

4. reactjs-popup

The reactjs-popup popover library is an excellent choice for those seeking versatility in a simple package. It offers a fully accessible popup component, improving the user experience for individuals who rely on keyboard navigation.

Setting up and using reactjs-popup

Installing reactjs-popup is straightforward. Simply use this command:

npm i reactjs-popup
Enter fullscreen mode Exit fullscreen mode

The code below demonstrates how you can create a popover using reactjs-popup:

import Popup from "reactjs-popup";

const TableRow = ({ name, email, popup }) => {
  return (
    // ...
    <Popup
      trigger={
        <div className="btn-container">
          <Button label="Admin" />
          <RxCaretSort />
        </div>
      }
      position={["top left", "top right", "bottom left", "bottom right"]}
      closeOnDocumentClick
      arrowStyle={{ color: "#2a2e2d" }}
    >
      <div className="popover-content">{popup}</div>
    </Popup>
    // ...
  );
};
Enter fullscreen mode Exit fullscreen mode

This represents the simplest popover implementation for our hypothetical project that we’ve seen so far. reactjs-popup provides us with an easy-to-use Popup component and handles complex positioning very well. Feel free to explore the source code and demo on CodeSandbox.

5. React Joyride: Guided tours with popovers

React Joyride takes a unique approach to popovers, specializing in popovers designed for guided tours within React applications. It's a standout choice for onboarding new users in a dynamic and engaging way.

Before we implement this library, let’s see a quick demo of what it entails: Demo Of Guided Tour With Popovers Created Using React Joyride Library To Walk User Through Using Demo Project

As we can see, React Joyride provides sufficient control over popover positioning to ensure a smooth user experience. It can adapt to different screen sizes, making it ideal for onboarding experiences.

Setting up and using React Joyride

Installing React Joyride is simple, requiring only the following command with no other dependencies:

npm i react-joyride
Enter fullscreen mode Exit fullscreen mode

To use this library, you need to define the steps for the guided tour. Each step will include a target property that points to the appropriate element:

import { useState } from "react";
import Joyride from "react-joyride";
// ...

const TableRow = ({ name, email, popup }) => {

  const [{ run, steps }] = useState({
    run: true,
    steps: [
      {
        content: <h2>Tour this app with me!</h2>,
        locale: { skip: <strong>SKIP</strong> },
        placement: "center",
        target: "body"   
      }, 
      { 
        content: <h2>User's details</h2>,
        placement: "bottom",
        target: ".user"
      },
      {
        content: <h2>Assign a role</h2>,
        placement: "bottom",
        target: ".btn-container"
      },
      {
        content: <h2>Take me to next section</h2>,
        placement: "bottom",
        target: ".navigation"
      }
    ]
  });

  return (
    <>
      <Joyride
        steps={steps}
        continuous
        hideCloseButton
        run={run}
        scrollToFirstStep
        showProgress
        showSkipButton
      />
      {/* ... */}
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

You can see the complete code on CodeSandbox.

Comparison table for React popover libraries

This comparison table for all five popover libraries may be a helpful resource as you decide which option is best for your project:

Library Purpose Popularity Community Best for
Popper.js Positioning engine library for popup UI elements 29.6K GitHub stars, 12 million weekly npm downloads Active and supportive Applications requiring precise positioning of UI elements
react-tiny-popover Lightweight React library for creating customizable popovers 405 GitHub stars, 93.5K weekly npm downloads Active community Projects requiring a simple and customizable popover solution
react-laag React library for creating position-aware UI components 884 GitHub stars, 138K weekly npm downloads Active community Projects requiring precise control over the positioning of popovers
reactjs-popup React library for creating customizable popups 1.7k GitHub stars, 110K weekly npm downloads Active community Projects requiring a versatile and customizable popup solution.
React Joyride React library for creating guided tours and walkthroughs 6k GitHub stars, 239K weekly npm downloads Active community Projects requiring interactive onboarding experiences

Conclusion

Popovers provide an interactive and user-friendly way to enhance your React applications, but they can be challenging to implement from scratch. Popover libraries can make it easier to deal with various edge cases so your popover components look and work great on any device or screen size.

In the vast landscape of React popover libraries, the choice boils down to your project's specific needs. Whether you're aiming for the lightweight simplicity of reactjs-popup and react-tiny-popover or the robust positioning capability of Popper.js, you can choose from a diverse range of options.

We explored five popover and positioning libraries in this article. Experiment with these libraries, considering factors such as ease of use, customization capabilities, responsiveness, and community support, to find the popover solution that best fits your project.

In the end, the journey through these libraries not only enhances your React skills but also empowers you to create compelling and user-friendly popover interfaces.

See all project demos here.


Get set up with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.

NPM:

$ npm i --save logrocket 

// Code:

import LogRocket from 'logrocket'; 
LogRocket.init('app/id');
Enter fullscreen mode Exit fullscreen mode

Script Tag:

Add to your HTML:

<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
Enter fullscreen mode Exit fullscreen mode

3.(Optional) Install plugins for deeper integrations with your stack:

  • Redux middleware
  • ngrx middleware
  • Vuex plugin

Get started now

Top comments (1)

Collapse
 
arisunarya profile image
Ari Sunarya • Edited

nice share, you should try floating-ui, it's the evolution of Popper.js