@popperjs/core vs react-overlays vs react-popper vs react-popper-tooltip vs react-tippy vs react-tooltip
React Positioning Engines and Tooltip Implementations
@popperjs/corereact-overlaysreact-popperreact-popper-tooltipreact-tippyreact-tooltipSimilar Packages:

React Positioning Engines and Tooltip Implementations

These libraries solve the problem of positioning elements relative to others — commonly used for tooltips, popovers, and dropdowns. @popperjs/core is the framework-independent engine that calculates positions. react-popper and react-overlays provide React-specific hooks and components to use that engine. react-popper-tooltip, react-tippy, and react-tooltip build on top of these to offer ready-made tooltip components with varying levels of customization and maintenance status.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
@popperjs/core032,5311.46 MB453 years agoMIT
react-overlays0892249 kB114-MIT
react-popper02,49579.1 kB39-MIT
react-popper-tooltip025093.3 kB12-MIT
react-tippy0981-1036 years agoMIT
react-tooltip03,795889 kB106 months agoMIT

React Positioning and Tooltips: Core Engines vs Ready-Made Components

When building interactive interfaces, you often need elements to float near others — like tooltips on hover or dropdowns on click. The packages in this comparison range from raw positioning engines to fully styled components. Understanding where each sits in the stack helps you pick the right tool for your architecture.

🏗️ Architecture Level: Engine vs Wrapper vs Component

The first decision is how much control you need versus how much work you want to do.

@popperjs/core is the raw engine. It calculates where an element should go. It does not know about React.

// @popperjs/core: Vanilla JS inside useEffect
import { createPopper } from '@popperjs/core';

useEffect(() => {
  const popper = createPopper(referenceEl, popperEl, {
    placement: 'top'
  });
  return () => popper.destroy();
}, []);

react-popper wraps the engine in React hooks. It manages the lifecycle for you.

// react-popper: Hook-based integration
import { usePopper } from 'react-popper';

const { styles, attributes } = usePopper(referenceRef, popperRef, {
  placement: 'top'
});

react-overlays provides lower-level components often used by libraries like React Bootstrap.

// react-overlays: Component-based primitive
import { Overlay } from 'react-overlays';

<Overlay show={show} target={target} placement="top">
  {({ style }) => <div style={style}>Content</div>}
</Overlay>

react-popper-tooltip adds tooltip logic (show/hide states) on top of react-popper.

// react-popper-tooltip: Hook with state logic
import { useTooltipTrigger } from 'react-popper-tooltip';

const { getTriggerProps, getTooltipProps } = useTooltipTrigger();

react-tippy is a legacy wrapper around Tippy.js. It offers a simple component but is less maintained.

// react-tippy: Simple component (Legacy)
import { Tooltip } from 'react-tippy';

<Tooltip title="Hello"> 
  <button>Hover</button>
</Tooltip>

react-tooltip is a modern, styled tooltip component (v5). It handles everything out of the box.

// react-tooltip: Ready-made component (v5)
import { Tooltip } from 'react-tooltip';

<button data-tooltip-id="my-tooltip">Hover</button>
<Tooltip id="my-tooltip" content="Hello" />

🎨 Styling and Theming: Bring Your Own vs Built-In

Some packages give you a blank canvas, while others paint the picture for you.

@popperjs/core gives no styles. You must write CSS for positioning and visibility.

/* @popperjs/core: Manual CSS required */
.popper { position: absolute; }

react-popper returns style objects you must apply to elements.

// react-popper: Apply styles manually
<div ref={popperRef} style={styles.popper} {...attributes.popper} />

react-overlays expects you to handle styling via the render prop or children.

// react-overlays: Style passed in props
<Overlay>{({ style }) => <div style={style} />}</Overlay>

react-popper-tooltip separates logic from style. You define the look.

// react-popper-tooltip: Custom style implementation
<div {...getTooltipProps({ className: 'my-tooltip' })} />

react-tippy comes with default themes but allows customization via props.

// react-tippy: Built-in themes
<Tooltip theme="light" content="Hello">Btn</Tooltip>

react-tooltip includes built-in styles and supports custom class names.

// react-tooltip: Built-in styles + custom class
<Tooltip className="custom-tooltip" content="Hello" />

⚙️ API Style: Hooks vs Components

Modern React favors hooks, but some libraries still rely on components.

@popperjs/core uses a function call, not a hook or component.

// @popperjs/core: Function call
const instance = createPopper(ref1, ref2);

react-popper uses a custom hook (usePopper).

// react-popper: Hook
const { styles } = usePopper(ref1, ref2);

react-overlays uses a Component with render props.

// react-overlays: Component
<Overlay>{props => <div />}</Overlay>

react-popper-tooltip uses a custom hook (useTooltipTrigger).

// react-popper-tooltip: Hook
const { getTriggerProps } = useTooltipTrigger();

react-tippy uses a Wrapper Component.

// react-tippy: Wrapper Component
<Tooltip><button /></Tooltip>

react-tooltip uses a Separate Component linked by ID or wrapper.

// react-tooltip: Linked Component
<button data-tooltip-id="id" /><Tooltip id="id" />

🛠️ Maintenance and Ecosystem Health

Choosing a library means trusting its maintainers. Some tools are actively updated, while others are fading.

@popperjs/core is the foundation. It is stable and widely used by many other libraries.

react-popper is the official React wrapper. It is actively maintained and follows React best practices.

react-overlays is maintained by the React Bootstrap team. It is stable but tied to that ecosystem.

react-popper-tooltip is maintained and updated to work with recent React versions.

react-tippy is considered legacy. The main tippy.js library recommends using it directly with React refs instead of this wrapper.

react-tooltip recently released version 5, which is a complete rewrite. It is actively maintained for new projects.

📊 Summary: Key Differences

PackageTypeReact IntegrationStylingStatus
@popperjs/coreEngineNone (Vanilla JS)NoneStable
react-popperWrapperHooks (usePopper)ManualActive
react-overlaysPrimitiveComponents (Overlay)ManualActive
react-popper-tooltipLogic LayerHooks (useTooltip)ManualActive
react-tippyComponentWrapper ComponentBuilt-inLegacy
react-tooltipComponentLinked ComponentBuilt-inActive (v5)

💡 Final Recommendation

For Custom Design Systems: Start with react-popper. It gives you the power of the engine with React hooks, letting you build accessible components that match your brand exactly.

For Quick Implementation: Use react-tooltip (v5). It gets tooltips on the screen fast with minimal code and handles accessibility basics for you.

For Legacy or Specific Needs: Use react-overlays if you are deep in the React Bootstrap ecosystem. Avoid react-tippy for new work — the community has moved toward using tippy.js directly or switching to react-popper.

For Maximum Control: Use @popperjs/core only if you are building a framework-agnostic library or need to support environments outside of React.

How to Choose: @popperjs/core vs react-overlays vs react-popper vs react-popper-tooltip vs react-tippy vs react-tooltip

  • @popperjs/core:

    Choose @popperjs/core if you need maximum control and are willing to write the React integration yourself. It is best for non-React projects or when building a custom component library where you want to manage the lifecycle without extra abstractions.

  • react-overlays:

    Choose react-overlays if you are already using React Bootstrap or need low-level overlay primitives like OverlayTrigger. It is suitable for projects that require robust positioning logic tied to the Bootstrap ecosystem.

  • react-popper:

    Choose react-popper if you want the official React hooks for Popper.js without extra opinionated features. It is ideal for custom dropdowns or popovers where you need precise control over styles and placement logic.

  • react-popper-tooltip:

    Choose react-popper-tooltip if you need accessible tooltip logic built on react-popper but want to handle styling yourself. It works well for teams that want standard tooltip behavior without locking into a specific design system.

  • react-tippy:

    Avoid react-tippy for new projects as it is largely considered legacy compared to direct tippy.js usage. Only choose it if you are maintaining an older codebase that already depends on this specific wrapper.

  • react-tooltip:

    Choose react-tooltip if you want a quick, styled tooltip solution with minimal setup. It is best for applications that need functional tooltips fast without building custom positioning logic from scratch.

README for @popperjs/core

Popper

Tooltip & Popover Positioning Engine

npm version npm downloads per month (popper.js + @popperjs/core) Rolling Versions


Positioning tooltips and popovers is difficult. Popper is here to help!

Given an element, such as a button, and a tooltip element describing it, Popper will automatically put the tooltip in the right place near the button.

It will position any UI element that "pops out" from the flow of your document and floats near a target element. The most common example is a tooltip, but it also includes popovers, drop-downs, and more. All of these can be generically described as a "popper" element.

Demo

Popper visualized

Docs

We've created a Migration Guide to help you migrate from Popper 1 to Popper 2.

To contribute to the Popper website and documentation, please visit the dedicated repository.

Why not use pure CSS?

  • Clipping and overflow issues: Pure CSS poppers will not be prevented from overflowing clipping boundaries, such as the viewport. It will get partially cut off or overflows if it's near the edge since there is no dynamic positioning logic. When using Popper, your popper will always be positioned in the right place without needing manual adjustments.
  • No flipping: CSS poppers will not flip to a different placement to fit better in view if necessary. While you can manually adjust for the main axis overflow, this feature cannot be achieved via CSS alone. Popper automatically flips the tooltip to make it fit in view as best as possible for the user.
  • No virtual positioning: CSS poppers cannot follow the mouse cursor or be used as a context menu. Popper allows you to position your tooltip relative to any coordinates you desire.
  • Slower development cycle: When pure CSS is used to position popper elements, the lack of dynamic positioning means they must be carefully placed to consider overflow on all screen sizes. In reusable component libraries, this means a developer can't just add the component anywhere on the page, because these issues need to be considered and adjusted for every time. With Popper, you can place your elements anywhere and they will be positioned correctly, without needing to consider different screen sizes, layouts, etc. This massively speeds up development time because this work is automatically offloaded to Popper.
  • Lack of extensibility: CSS poppers cannot be easily extended to fit any arbitrary use case you may need to adjust for. Popper is built with extensibility in mind.

Why Popper?

With the CSS drawbacks out of the way, we now move on to Popper in the JavaScript space itself.

Naive JavaScript tooltip implementations usually have the following problems:

  • Scrolling containers: They don't ensure the tooltip stays with the reference element while scrolling when inside any number of scrolling containers.
  • DOM context: They often require the tooltip move outside of its original DOM context because they don't handle offsetParent contexts.
  • Compatibility: Popper handles an incredible number of edge cases regarding different browsers and environments (mobile viewports, RTL, scrollbars enabled or disabled, etc.). Popper is a popular and well-maintained library, so you can be confident positioning will work for your users on any device.
  • Configurability: They often lack advanced configurability to suit any possible use case.
  • Size: They are usually relatively large in size, or require an ancient jQuery dependency.
  • Performance: They often have runtime performance issues and update the tooltip position too slowly.

Popper solves all of these key problems in an elegant, performant manner. It is a lightweight ~3 kB library that aims to provide a reliable and extensible positioning engine you can use to ensure all your popper elements are positioned in the right place.

When you start writing your own popper implementation, you'll quickly run into all of the problems mentioned above. These widgets are incredibly common in our UIs; we've done the hard work figuring this out so you don't need to spend hours fixing and handling numerous edge cases that we already ran into while building the library!

Popper is used in popular libraries like Bootstrap, Foundation, Material UI, and more. It's likely you've already used popper elements on the web positioned by Popper at some point in the past few years.

Since we write UIs using powerful abstraction libraries such as React or Angular nowadays, you'll also be glad to know Popper can fully integrate with them and be a good citizen together with your other components. Check out react-popper for the official Popper wrapper for React.

Installation

1. Package Manager

# With npm
npm i @popperjs/core

# With Yarn
yarn add @popperjs/core

2. CDN

<!-- Development version -->
<script src="https://unpkg.com/@popperjs/core@2/dist/umd/popper.js"></script>

<!-- Production version -->
<script src="https://unpkg.com/@popperjs/core@2"></script>

3. Direct Download?

Managing dependencies by "directly downloading" them and placing them into your source code is not recommended for a variety of reasons, including missing out on feat/fix updates easily. Please use a versioning management system like a CDN or npm/Yarn.

Usage

The most straightforward way to get started is to import Popper from the unpkg CDN, which includes all of its features. You can call the Popper.createPopper constructor to create new popper instances.

Here is a complete example:

<!DOCTYPE html>
<title>Popper example</title>

<style>
  #tooltip {
    background-color: #333;
    color: white;
    padding: 5px 10px;
    border-radius: 4px;
    font-size: 13px;
  }
</style>

<button id="button" aria-describedby="tooltip">I'm a button</button>
<div id="tooltip" role="tooltip">I'm a tooltip</div>

<script src="https://unpkg.com/@popperjs/core@^2.0.0"></script>
<script>
  const button = document.querySelector('#button');
  const tooltip = document.querySelector('#tooltip');

  // Pass the button, the tooltip, and some options, and Popper will do the
  // magic positioning for you:
  Popper.createPopper(button, tooltip, {
    placement: 'right',
  });
</script>

Visit the tutorial for an example of how to build your own tooltip from scratch using Popper.

Module bundlers

You can import the createPopper constructor from the fully-featured file:

import { createPopper } from '@popperjs/core';

const button = document.querySelector('#button');
const tooltip = document.querySelector('#tooltip');

// Pass the button, the tooltip, and some options, and Popper will do the
// magic positioning for you:
createPopper(button, tooltip, {
  placement: 'right',
});

All the modifiers listed in the docs menu will be enabled and "just work", so you don't need to think about setting Popper up. The size of Popper including all of its features is about 5 kB minzipped, but it may grow a bit in the future.

Popper Lite (tree-shaking)

If bundle size is important, you'll want to take advantage of tree-shaking. The library is built in a modular way to allow to import only the parts you really need.

import { createPopperLite as createPopper } from '@popperjs/core';

The Lite version includes the most necessary modifiers that will compute the offsets of the popper, compute and add the positioning styles, and add event listeners. This is close in bundle size to pure CSS tooltip libraries, and behaves somewhat similarly.

However, this does not include the features that makes Popper truly useful.

The two most useful modifiers not included in Lite are preventOverflow and flip:

import {
  createPopperLite as createPopper,
  preventOverflow,
  flip,
} from '@popperjs/core';

const button = document.querySelector('#button');
const tooltip = document.querySelector('#tooltip');

createPopper(button, tooltip, {
  modifiers: [preventOverflow, flip],
});

As you make more poppers, you may be finding yourself needing other modifiers provided by the library.

See tree-shaking for more information.

Distribution targets

Popper is distributed in 3 different versions, in 3 different file formats.

The 3 file formats are:

  • esm (works with import syntax — recommended)
  • umd (works with <script> tags or RequireJS)
  • cjs (works with require() syntax)

There are two different esm builds, one for bundler consumers (e.g. webpack, Rollup, etc..), which is located under /lib, and one for browsers with native support for ES Modules, under /dist/esm. The only difference within the two, is that the browser-compatible version doesn't make use of process.env.NODE_ENV to run development checks.

The 3 versions are:

  • popper: includes all the modifiers (features) in one file (default);
  • popper-lite: includes only the minimum amount of modifiers to provide the basic functionality;
  • popper-base: doesn't include any modifier, you must import them separately;

Below you can find the size of each version, minified and compressed with the Brotli compression algorithm:

Hacking the library

If you want to play with the library, implement new features, fix a bug you found, or simply experiment with it, this section is for you!

First of all, make sure to have Yarn installed.

Install the development dependencies:

yarn install

And run the development environment:

yarn dev

Then, simply open one the development server web page:

# macOS and Linux
open localhost:5000

# Windows
start localhost:5000

From there, you can open any of the examples (.html files) to fiddle with them.

Now any change you will made to the source code, will be automatically compiled, you just need to refresh the page.

If the page is not working properly, try to go in "Developer Tools > Application > Clear storage" and click on "Clear site data".
To run the examples you need a browser with JavaScript modules via script tag support.

Test Suite

Popper is currently tested with unit tests, and functional tests. Both of them are run by Jest.

Unit Tests

The unit tests use JSDOM to provide a primitive document object API, they are used to ensure the utility functions behave as expected in isolation.

Functional Tests

The functional tests run with Puppeteer, to take advantage of a complete browser environment. They are currently running on Chromium, and Firefox.

You can run them with yarn test:functional. Set the PUPPETEER_BROWSER environment variable to firefox to run them on the Mozilla browser.

The assertions are written in form of image snapshots, so that it's easy to assert for the correct Popper behavior without having to write a lot of offsets comparisons manually.

You can mark a *.test.js file to run in the Puppeteer environment by prepending a @jest-environment puppeteer JSDoc comment to the interested file.

Here's an example of a basic functional test:

/**
 * @jest-environment puppeteer
 * @flow
 */
import { screenshot } from '../utils/puppeteer.js';

it('should position the popper on the right', async () => {
  const page = await browser.newPage();
  await page.goto(`${TEST_URL}/basic.html`);

  expect(await screenshot(page)).toMatchImageSnapshot();
});

You can find the complete jest-puppeteer documentation here, and the jest-image-snapshot documentation here.

License

MIT