Skip to content

Migration

Learn how to migrate from Popper v2 to Floating UI.

Rebranding: The library was rebranded as it offers more packages and functionality, like the new React package that provides interactions, while Popper only ever offered positioning.

New API: The goal of the new API was to make Floating UI lower-level and act more like CSS in which you progressively add properties to achieve the desired positioning behavior, without pre-configuring anything.

In addition, we wanted to make the configuration more ergonomic, the library smaller and fully tree-shakeable (so new features don’t bloat bundles if unused), and also wanted to support React Native/Canvas as they had been requested, which is now supported.

Different, but familiar: Floating UI forked Popper 2 and shares a lot of similarities. While the main external positioning function changed, many other parts of the API are similar.

Similarities include:

  • The order of arguments and types of the positioning function are practically identical.
  • placement and strategy are the same, passed as a third argument options object.
  • middleware in Floating UI is conceptually similar to modifiers from Popper 2.
  • Options passed to middleware have very similar configuration as Popper 2, and APIs like detectOverflow and virtual elements are almost identical.

Change dependencies

First thing’s first, uninstall @popperjs/core and install @floating-ui/dom.

npm uninstall @popperjs/core
npm install @floating-ui/dom

Positioning function change

Popper applied styles and added modifiers for you by default. Floating UI on the other hand is completely bare bones — you add what you need with nothing pre-configured.

In Popper, you called createPopper() like so and it would place the popper element at the bottom for you automatically.

import {createPopper} from '@popperjs/core';
 
createPopper(reference, popper);

Floating UI does not apply styles or add modifiers (now more generically called middleware) for you. Instead it’s pure and only returns data that you can use as you please.

Set up the initial styles on the floating element in your CSS:

.floating {
  width: max-content;
  position: absolute;
  top: 0;
  left: 0;
}

Then, apply the coordinates using the positioning data resolved by the function:

import {computePosition} from '@floating-ui/dom';
 
computePosition(referenceEl, floatingEl).then(({x, y}) => {
  Object.assign(floatingEl.style, {
    left: `${x}px`,
    top: `${y}px`,
  });
});

Read more about computePosition here.

Updating the position automatically

computePosition() is not stateful, it only positions your element once.

Popper added listeners automatically to update the position on scroll and resize. In Floating UI, you add this yourself, and it’s much more explicit that it needs to be cleaned up:

import {computePosition, autoUpdate} from '@floating-ui/dom';
 
// When the floating element is open on the screen
const cleanup = autoUpdate(referenceEl, floatingEl, () => {
  computePosition(referenceEl, floatingEl).then(({x, y}) => {
    Object.assign(floatingEl.style, {
      left: `${x}px`,
      top: `${y}px`,
    });
  });
});
 
// When the floating element is removed from the screen
cleanup();

Floating UI also adds ResizeObserver listeners by default, unlike Popper, handling an additional update edge case.

Read more about autoUpdate here.

Configure middleware

Floating UI honors the placement you passed in and does not modify it by default:

import {computePosition} from '@floating-ui/dom';
 
computePosition(referenceEl, floatingEl, {
  placement: 'top',
}).then(({x, y}) => {
  // ...
});

Even if the floating element will overflow the top of the screen, it will still be placed and anchored there.

If you’d like to directly match Popper’s default behavior to prevent collisions, add in flip(), shift() and limitShift() in the following order:

import {
  computePosition,
  flip,
  shift,
  limitShift,
} from '@floating-ui/dom';
 
computePosition(referenceEl, floatingEl, {
  placement: 'top',
  middleware: [flip(), shift({limiter: limitShift()})],
}).then(({x, y}) => {
  // ...
});

Unlike Popper 2, the order of the array matters and is not adjusted for you. The Middleware page explains the concept in detail. Essentially, if you place one of the middleware before or after another, the positioning result can change. This helps enable full control as sometimes you want different behavior based on the ordering.

The preventOverflow modifier from Popper is now called shift. This is because technically many modifiers in Popper 2 “prevented overflow”, which does not describe what it is actually doing unlike shift.

You’ll hopefully notice the API is much more ergonomic at the point of the function call.

Popper 2:

createPopper(reference, popper, {
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: [0, 10],
      },
    },
  ],
});

Floating UI:

computePosition(referenceEl, floatingEl, {
  middleware: [offset(10)],
});

A lot of the options passed in to middleware options are similar or the same as Popper 2. To learn about their options, you can read their pages on the sidebar on the left.

Arrows styling

In Popper you could add a data-popper-arrow element inside your popper and it’d automatically be picked up and styled.

As Floating UI is pure, you now handle these styles yourself, and you always pass an element in manually. You can read more about this on the arrow middleware page.

Auto placement is now a middleware

In Popper 2, this was part of flip but now it’s separate and is no longer a string option for placement.

Popper 2:

import {createPopper} from '@popperjs/core';
 
createPopper(reference, popper, {
  placement: 'auto',
});

Floating UI:

import {computePosition, autoPlacement} from '@floating-ui/dom';
 
computePosition(referenceEl, floatingEl, {
  middleware: [autoPlacement()],
});

Other

APIs like detectOverflow and virtual elements are basically the same as Popper 2 with only minor differences.

detectOverflow() must now be called with the await keyword before it but accepts the same options as Popper 2 with minor differences.

Conclusion

If you think something is missing or are confused, you can open a Discussion on the GitHub repo and we’ll try to improve this page.