Google News
logo
Popper.js Interview Questions
Popper.js is an Opensource javascript framework for showing/hiding/reusing beautiful popovers and tooltips on web applications. In this tutorial you'll learn how to use Popper by building a basic tooltip. Remember, Popper is not a tooltip library, it's the foundation to build one!
For the left and top placements, Popper relies on HTML Standards Mode for the computeStyles modifier's adaptive option. A problem will occur in Quirks Mode when the <body> is the popper element's offsetParent, and it's taller than the viewport.
 
To fix it, use the Standards Mode doctype :
<!DOCTYPE html>
<html>
  <!-- ... -->
</html>
Additionally, make sure your popper element (tooltip, popover, etc...) CSS doesn't include some positioning styles (such as top, left, right, bottom, and transform) because they could interfere with the Popper positioning logic.
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.
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"></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>
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 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;
A Popper is used to show the part of the content on top of another. It’s an alternative feature for react-popper. Material UI for React has this component available for us, and it is simple and very easy to integrate. For perfect positioning, it uses 3rd party library which is Popper.js. We can use the Popper Component in ReactJS using the following approach.
 
Creating React Application And Installing Module :
 
Step 1 : Create a React application using the following command.
npx create-react-app foldername
Step 2 : After creating your project folder i.e. foldername, move to it using the following command.
cd foldername
Step 3 : After creating the ReactJS application, Install the material-ui modules using the following command.
npm install @material-ui/core

Example :  Now write down the following code in the App.js file. Here, App is our default component where we have written our code. 

import React from 'react';
import Popper from '@material-ui/core/Popper';

export default function App() {
  const [anchorEl, setAnchorEl] = React.useState(null);
  const open = Boolean(anchorEl);
    return (
         <div style={{ display: 'block', padding: 30 }}>
         <h4>How to use Popper Component in ReactJS?</h4>
      <button type="button" onClick={(event) => {
        setAnchorEl(anchorEl ? null : event.currentTarget);
      }}>
        Click Me to Toggle Popper
      </button>
      <Popper
        id={open ? 'simple-popper' : undefined}
        open={open}
        anchorEl={anchorEl}>
        <div style={{
          padding: 2,
          border: '1px solid',
          backgroundColor: 'gray',
        }}>Greetings from Free Time Learn</div>
      </Popper>
    </div>
  );
}

Step to Run Application : Run the application using the following command from the root directory of the project.

npm start

Output : Now open your browser and go to http://localhost:3000/, you will see the following output.
Popper-ReactJS

Our tooltip is currently just a box. In many UI design systems, this is all tooltips need, but others prefer to have an arrow that points toward the reference element.
 
Add an arrow element with a data-popper-arrow attribute like so:
<div id="tooltip" role="tooltip">
  My tooltip
  <div id="arrow" data-popper-arrow></div>
</div>
Now for styling :
#arrow,
#arrow::before {
  position: absolute;
  width: 8px;
  height: 8px;
  background: inherit;
}

#arrow {
  visibility: hidden;
}

#arrow::before {
  visibility: visible;
  content: '';
  transform: rotate(45deg);
}
The ::before pseudo-element is required because Popper uses transform to position the arrow inside the popper, but we want to use our own transform to rotate the arrow box into a diamond.
 
Now we need to offset the arrow depending on the popper's current placement. Popper provides this information with the data-popper-placement attribute :
#tooltip[data-popper-placement^='top'] > #arrow {
  bottom: -4px;
}

#tooltip[data-popper-placement^='bottom'] > #arrow {
  top: -4px;
}

#tooltip[data-popper-placement^='left'] > #arrow {
  right: -4px;
}

#tooltip[data-popper-placement^='right'] > #arrow {
  left: -4px;
}
Note : Why the ^=? This means "starts with", because we can also have variation placements like top-start.

Output : 
Popper
Our arrow currently overlaps the reference, we can give it a distance of 8px using the offset modifier:
const popperInstance = Popper.createPopper(button, tooltip, {
  modifiers: [
    {
      name: 'offset',
      options: {
        offset: [0, 8],
      },
    },
  ],
});

Output : 
Popper

Imports :

The createPopper constructor is at the heart of Popper. It allows you to create individual popper instances (objects) with state and methods.
Imports
// esm
import { createPopper } from '@popperjs/core';

// cjs
const { createPopper } = require('@popperjs/core');

// umd
const { createPopper } = Popper;

 

Usage :

const reference = document.querySelector('#reference');
const popper = document.querySelector('#popper');

createPopper(reference, popper, {

  // options

});

 

Options :

type Options = {|
  placement: Placement, // "bottom"
  modifiers: Array<$Shape<Modifier<any>>>, // []
  strategy: PositioningStrategy, // "absolute",
  onFirstUpdate?: ($Shape<State>) => void, // undefined
|};

type Placement =
  | 'auto'
  | 'auto-start'
  | 'auto-end'
  | 'top'
  | 'top-start'
  | 'top-end'
  | 'bottom'
  | 'bottom-start'
  | 'bottom-end'
  | 'right'
  | 'right-start'
  | 'right-end'
  | 'left'
  | 'left-start'
  | 'left-end';
type Strategy = 'absolute' | 'fixed';

 

placement : Describes the preferred placement of the popper. Modifiers like flip may change the placement of the popper to make it fit better.

createPopper(reference, popper, {

  placement: 'right-start',

});

The "auto" placements will choose the side with most space.

 

modifiers : Describes the array of modifiers to add or configure. The default (full) version of Popper includes all modifiers listed in the menu.

createPopper(reference, popper, {
  modifiers: [
    {
      name: 'flip',
      enabled: false,
    },
  ],
});

 

strategy : Describes the positioning strategy to use. By default, it is absolute, which in the simplest cases does not require repositioning of the popper.

If your reference element is in a fixed container, use the fixed strategy:

createPopper(reference, popper, {

  strategy: 'fixed',

});
The popperGenerator constructor generates a createPopper function. This allows you to configure createPopper with the functionality you need instead of needing to pass the same defaults each time.
 
Imports :
// esm
import { popperGenerator } from '@popperjs/core';

// cjs
const { popperGenerator } = require('@popperjs/core');

// umd
const { popperGenerator } = Popper;
Usage :
const createPopper = popperGenerator({
  defaultOptions: { placement: 'top' },
  defaultModifiers: [popperOffsets, computeStyles, applyStyles, eventListeners],
});

// Now your custom `createPopper` is ready to use.
Types : 
type PopperGenerator = (options?: PopperGeneratorOptions) => CreatePopper;

type PopperGeneratorOptions = {
  defaultModifiers?: Array<Modifier<any>>,
  defaultOptions?: $Shape<Options>,
};
Popper relies on a set of steps, executed in a precise order, in order to provide the functionalities you expect from it.
 
Everything starts with the popper initialization, executed by calling createPopper.
 
As first thing, all the modifier's effects are ran. After that, it's the turn of the modifier's main logic.
 
At this point, your popper has been properly positioned, and its position is ready to be updated in different ways.

Manual update : You can ask Popper to recompute your tooltip's position by running instance.update().
 
This method will return a promise, that will be resolved with the updated State, from where you will optionally be able to read the updated positions.
const state = await popperInstance.update();
Set new options : You can also trigger an update by setting new options to your Popper instance. This, internally, will call the update() method.
const state = await popperInstance.setOptions({ placement: 'bottom' });
Event Listeners : You can also have Popper automatically update the position when certain events are fired, to learn more visit the Event Listeners Modifier page.
 
Hook into the lifecycle : There are situations where you may need to hook into the Popper's lifecycle to perform some additional logic.
 
If all you need is a callback ran after Popper positioned the element the first time, you can define the onFirstUpdate property in the Popper options:
createPopper(referenceElement, popperElement, {
  onFirstUpdate: state => console.log('Popper positioned on', state.placement),
});
If, instead, you want to run some logic on each update cycle, the best way to do that is to define your own custom modifier.
 
If you want to make sure to run your logic after all the other modifiers have ran, we suggest to set afterWrite as your custom modifier's phase.
Popper is built using an extensible core, which provides the foundation used to deliver all the functionalities offered by the library.
 
All the useful functionalities provided by the library are implemented as Popper modifiers. They are plugins, or middlewares, that can hook into the lifecycle of Popper, and add additional logic to the positioning operations provided by default by Popper. They effectively "modify" the popper state in some fashion, adding functionality, hence the term "modifiers".
It is possible to add custom modifiers, written by you, by defining them in the options.modifiers array during a Popper instantation.

name : The name is used as an identifier to make it possible to refer to the modifier from other parts of the library. For example, you can add an object to the options.modifier array with the name property, and the options property populated with some custom options, to override the options of a built-in modifier.
createPopper(reference, popper, {
  modifiers: [
    {
      name: 'flip',
      options: {
        fallbackPlacements: ['top', 'bottom'],
      },
    },
  ],
});
enabled : If set to true, the modifier will be executed during the Popper lifecycle, otherwise, it will be ignored.
 
phase : Popper's modifiers lifecycle is divided into 3 core phases: read, main, and write. This is done to optimize the library so that its access to the DOM is grouped together rather than scattered around the whole lifecycle.
 
The modifiers that need to read from the DOM should run in the read phase, the ones that only perform logic with algorithms should live in main, and the ones that write to the DOM should be under write.
 
Note that Popper provides a cache of DOM measurements in its state, so that modifiers can read them rather than querying the DOM, optimizing the overall execution time. This means you should rarely need to hook into the read phase.
 
For further granularity if needed, there are 2 other sub-phases: before and after. Here is the full list :
* beforeRead
* read
* afterRead
* beforeMain
* main
* afterMain
* beforeWrite
* write
* afterWrite
fn : This is the main function, used to provide the logic to the modifier.
 
There are cases when you may want to control the Popper lifecycle through a modifier – for example the flip modifier can alter the placement option, and if that happens, Popper is instructed to run all the modifiers again, so that they can react to the updated placement value. A modifier can reset the lifecycle by setting state.reset to true.
 
requires : Specifies the list of modifiers it depends upon. Popper will execute the modifiers in the correct order to allow the dependent modifier to access the data provided by the dependee modifier.
 
For example, offset relies on popperOffsets data, since it mutates its properties.
 
In short, the modifier depends upon this list of modifiers' data to work.
 
requiresIfExists : Specifies the list of modifiers it depends upon, but only if those modifiers actually exist.
 
For example, preventOverflow relies on offset, but only if offset actually exists, because offset mutates the popperOffsets data, which preventOverflow needs to read and mutate. If offset doesn't exist, preventOverflow will still work as normal.
 
In short, the modifier depends upon this list of modifiers' behavior (or mutations to dependent data from other modifiers) to work.
 
effect : This function allows you to execute arbitrary code (effects) before the first update cycle is ran. Perform effects in the function and return a cleanup function if necessary:
function effect() {
  // perform side effects
  return () => {
    // cleanup side effects
  };
}
Examples where this is useful involve code that is not necessary to be run on every update, rather only when the instance lifecycle changes (created, updated, or destroyed):
 
* The eventListeners modifier uses this to add and remove window/document event listeners which is not part of the main modifier update cycle.

* The arrow modifier uses this to add the element to state.elements. The arrow modifier is dependent on preventOverflow (run after), but preventOverflow depends on state.elements.arrow. Since effects are run before the first update cycle, the problem is resolved.

options : This is an object with all the properties used to configure the modifier.
 
data : This is the initial data provided to state.modifiersData.<MODIFIER_NAME>, which is shared to other modifiers to be read or manipulated.

Example modifier : A modifier that logs to the console when it's on the top (not very useful, but demonstrates each property):
const topLogger = {
  name: 'topLogger',
  enabled: true,
  phase: 'main',
  fn({ state }) {
    if (state.placement === 'top') {
      console.log('Popper is on the top');
    }
  },
};

createPopper(reference, popper, {
  modifiers: [topLogger],
});
A modifier is composed of an object with the following properties:
type Modifier = {|
  // Required properties
  name: string,
  enabled: boolean,
  phase: ModifierPhases,
  fn: (ModifierArguments<Options>) => ?State,

  // Optional properties
  requires?: Array<string>,
  requiresIfExists?: Array<string>,
  effect?: (ModifierArguments<Options>) => ?() => void,
  options?: {},
  data?: {},
|};

type ModifierArguments<Options: Obj> = {
  state: $Shape<State>,
  instance: Instance,
  options: $Shape<Options>,
  name: string,
};
The popperOffsets modifier is at the core of Popper, it computes the offsets needed to position the popper element near the reference element.
 
In other words, without this modifier you would have no reason to use Popper at all 😄
 
This piece of logic is written as a modifier because it makes it possible to run additional custom modifiers before it, for example, if you need to read from the DOM and manipulate the measurements made by Popper at the beginning of the lifecycle.
 
Additionally, you may want to replace this modifier with your own one, to address very specific cases not covered by the default one.

Phase :
read

 

Options : This modifier currently has no options.
 
Data : 
type Data = {
  x: number,
  y: number,
};
While the library provides access to all utils, use these at your own risk. Only the utils listed in the documentation menu will strictly follow SemVer. We may break or remove undocumented utils between patch releases.
The detectOverflow utility returns an object of overflow offsets of the popper or reference element relative to a given boundary.
import { detectOverflow } from '@popperjs/core';

const overflow = detectOverflow(state, options);
You can use this utility within your own custom modifiers.

State : The first argument is the popper instance state that gets passed in to modifiers.
 
Options :
type Options = {
  placement: Placement, // state.placement
  elementContext: Context, // "popper"
  boundary: Boundary, // "clippingParents"
  rootBoundary: RootBoundary, // "viewport"
  altBoundary: boolean, // false
  padding: Padding, // 0
};

// Below are the relative types
type Context = 'reference' | 'popper';
type Boundary = 'clippingParents' | HTMLElement | Array<HTMLElement>;
type RootBoundary = 'document' | 'viewport';
type Padding =
  | number
  | {|
      top?: number,
      right?: number,
      bottom?: number,
      left?: number,
    |};
placement : This will check the overflow when the popper is given this placement. By default, it's state.placement.
 
elementContext : This describes the element that is being checked for overflow relative to the boundary.
detectOverflow(state, {
  elementContext: 'reference', // 'popper' by default
});
boundary : This describes the area that the element will be checked for overflow relative to.
 
By default, it is "clippingParents", which are the scrolling containers that may cause the element to be partially or fully cut off.
const customBoundary = document.querySelector('#boundary');

detectOverflow(state, {
  boundary: customBoundary, // 'clippingParents' by default
});
rootBoundary : This describes the root boundary that will be checked for overflow. There are only two "roots" – the viewport and the document. "viewport" is default, which is the area of the document the user can actually see on the screen. "document" is the entire page which can be potentially scrolled.
detectOverflow(state, {
  rootBoundary: 'document', // 'viewport' by default
});
altBoundary : This describes whether to use the alt element's boundary. For example, if the elementContext is "popper", then it will check the reference's boundary context, and vice-versa.
detectOverflow(state, {
  altBoundary: true, // false by default
});
padding : Applies virtual padding to the boundary.
 
You can pass a number, which will be equal padding on all four sides, or an object containing side properties each with their own padding value.
detectOverflow(state, {
  // Same padding on all four sides
  padding: 8,
  // Different padding on certain sides – unspecified sides are 0
  padding: { top: 8, right: 16 },
});
Return value : 
// If the number is positive, the popper is overflowing by that number of pixels.
// When 0, or negative, the popper is within its boundaries.
type OverflowOffsets = {
  top: number,
  bottom: number,
  right: number,
  left: number,
};
Example : For your custom modifiers, ensure you add "offset" to requiresIfExists, as the util needs to have access to this information if offset exists in the modifier lifecycle:
import { detectOverflow } from '@popperjs/core';

const myModifier = {
  name: 'myModifier',
  enabled: true,
  phase: 'main',
  requiresIfExists: ['offset'],
  fn({ state }) {
    const overflow = detectOverflow(state);
    // ...
  },
};
detectOverflow only considers the offset modifier by default and reports values as though no other modifier is currently affecting it. This means if preventOverflow is enabled, its values should be taken into account (state.modifiersData.preventOverflow), along with any other potential modifier that alters popperOffsets.
You can use a virtual element instead of a real DOM element as the reference. This allows you to position the popper relative to a virtual area defined by any coordinates you desire.
 
A common use case for this is making the popper follow the mouse cursor, where the cursor acts as a point reference.
 
A virtual element is a plain object:
type VirtualElement = {|
  getBoundingClientRect: () => ClientRect | DOMRect,
  contextElement?: Element,
|};
contextElement is an optional property that describes the DOM context of the virtual element. This is necessary if getBoundingClientRect is derived from a real DOM element, and that element is within a different scrolling container context to the popper element.

Usage : This will make the popper follow the mouse cursor as seen in the demo above:
function generateGetBoundingClientRect(x = 0, y = 0) {
  return () => ({
    width: 0,
    height: 0,
    top: y,
    right: x,
    bottom: y,
    left: x,
  });
}

const virtualElement = {
  getBoundingClientRect: generateGetBoundingClientRect(),
};

const instance = createPopper(virtualElement, popper);

document.addEventListener('mousemove', ({ clientX: x, clientY: y }) => {
  virtualElement.getBoundingClientRect = generateGetBoundingClientRect(x, y);
  instance.update();
});
While Popper v2 saw a sizeable decrease in bundle size from v1 (-2 kB from ~7 kB minzipped to ~5 kB minzipped), this is still not optimal.
 
Popper addresses a lot of use cases and complexity, but to help keep consumers' bundle sizes small, we made the library tree-shakable. "Tree-shaking" is the process of eliminating unused code from your bundles, which is achieved by bundlers such as webpack, Rollup and Parcel.
 
If you're using the CDN, tree-shaking will not be available.
The recommended way to use Popper is to attach popper elements next to their reference elements.

This ensures that accessibility best practices are followed, such as keyboard navigation and screen reader support.
 
There are cases where you may want to attach popper elements to the top of the DOM, such as <body />, this is of course supported as well, but some measures should be taken to ensure that everything works in the best possible way.
 
To ensure the best possible performance, it's recommended to not attach elements directly to the <body /> element, but instead create a <div /> element right within the <body /> and use it as container of your poppers.
To pass these extra modifiers as default, you can use the popperGenerator function :
import {
  popperGenerator,
  defaultModifiers,
} from '@popperjs/core/lib/popper-lite';
import flip from '@popperjs/core/lib/modifiers/flip';
import preventOverflow from '@popperjs/core/lib/modifiers/preventOverflow';

const createPopper = popperGenerator({
  defaultModifiers: [...defaultModifiers, flip, preventOverflow],
});

// Now you can use `createPopper`
The most straightforward way to enable tree-shaking is to use Popper Lite and configure it with your needs.
 
Instead of :
// ❌ Imports all of Popper!
import { createPopper } from '@popperjs/core';
Use :
// ✅
import { createPopper } from '@popperjs/core/lib/popper-lite';
Popper Lite only comes with the following modifiers :
* popperOffsets
* computeStyles
* applyStyles
* eventListeners
 
This effectively achieves the bare minimum functionality, and is around 3 kB minzipped. If 3 kB is still too much for your application, we recommend trying out CSS tooltip alternatives which are as tiny as 1 kB. Please note that such libraries have many disadvantages that Popper doesn't, so assess the trade-offs where necessary.
 
Now, you'll need to add the modifiers you need. These are accessible under the @popperjs/core/lib/modifiers/ directory.
 
Generally, we recommend flip and preventOverflow since these are the most attractive reasons for using Popper in the first place:
import { createPopper } from '@popperjs/core/lib/popper-lite';
import flip from '@popperjs/core/lib/modifiers/flip';
import preventOverflow from '@popperjs/core/lib/modifiers/preventOverflow';

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