Skip to content
🍦 Minimalistic, small, positioning engine. Build for high-performance, minimal footprint and maximum control over positioning behavior.
HTML TypeScript JavaScript CSS
Branch: master
Clone or download

Latest commit

Simonwep Update
Latest commit 8b0a04a May 31, 2020


Type Name Latest commit message Commit time
Failed to load latest commit information.
.github/workflows Update README and CI workflow May 9, 2020
src Fix rounding issues May 19, 2020
tests Add tests for custom-containers May 19, 2020
.editorconfig Initial commit May 8, 2020
.eslintrc Initial commit May 8, 2020
.gitignore Initial commit May 8, 2020
LICENSE Initial commit May 8, 2020 Update May 30, 2020
jest-puppeteer.config.js Fix puppeteer test suite May 9, 2020
jest.config.js Add visual tests May 9, 2020
package-lock.json Release 1.3.0 May 19, 2020
package.json Release 1.3.0 May 19, 2020 Add visual tests May 9, 2020 Add visual tests May 9, 2020
tsconfig.json Initial commit May 8, 2020


Ultra Tiny, Opinionated Positioning Engine

gzip size brotli size Build Status Download count No dependencies JSDelivr download count Current version Support me

NanoPop is an ultra-tiny positioning engine. But wait, isn't there PopperJS? Yeah - and PopperJS is great! But there are tons of features that, in most cases, you just might not need. This library is only around ~ 700 Bytes brotlied (PopperJS is around 3kB).

When should I use Nanopop and not PopperJS?

  1. Situations where you want full control over positioning, including handling events such as scrolling, and manual resizing.
  2. Performance-critical cases with lots of elements [...] nanopop will only makes changes if you say so.
  3. Poppers with minimal footprint such as drop-downs and tooltips which don't require that much configurability.
  4. You might have some special needs about how your popper behaves. NanoPop could be used as super-class and you can, based on what's required, extend NanoPop as you will :)

This library was originally part of pickr - now ported to TS with tests and a few updates / bug-fixes.

Getting Started

Install via npm:

$ npm install nanopop

Install via yarn:

$ yarn add nanopop

Include directly via jsdelivr:

<script src=""></script>

Using JavaScript Modules:

import {NanoPop} from ''


const reference = document.querySelector('.btn');
const popper = document.querySelector('.dropdown');
const nanopop = new NanoPop(reference, popper);

// Updating the popper-position

⚠ The popper-element must have set position to fixed.

β„Ή Because the default-container is document.documentElement you might have to increase the height of the html element to make room for your popper (e.g. html {height: 100vh;})

All options

const nanopop = new NanoPop(reference, popper, {

    // The DOMRect of the container, this is the default:
    container: document.documentElement.getBoundingClientRect(),

    // Margin between the popper element and the reference
    margin: 8,

    // Preferred position, any combination of [top|right|bottom|left]-[start|middle|end] is valid.
    position: 'bottom-start',

    // Sometimes there's no way to position the popper element without clipping it.
    // Turn this on if you, in case there's no non-clipping position, want to apply the wanted position forcefully.
    // The .update() function will return false in any case it fails so you can handle this separately.
    // Attention: If this is set to false and you do not take care about handling the clipped element yourself it'll be positioned on the top-left corner of the container-element (most of the time this is the document element itself).
    forceApplyOnFailure: false,

    // In case the variant-part (start, middle or end) cannot be applied you can specify what (and if)
    // should be tried next.
    variantFlipOrder: {
        start: 'sme', // In case of -start try 'start' first, if that fails 'middle' and 'end' if both doesn't work.
        middle: 'mse',
        end: 'ems'

    // The same as variantFlipOrder, but if all variants fail you might want to try other positions.
    positionFlipOrder: {
        top: 'tbrl', // Try 'top' first, 'bottom' second, 'right' third and 'left' as latest position.
        right: 'rltb',
        bottom: 'btrl',
        left: 'lrbt'


  • nanopop.update(newOptions?: Partial<Options>) - Update the position and optionally update the options of this NanoPop instance. It'll return a position-pair (For example te for Top-End) or null based on if it was possible to find a position for the popper without clipping it.

Tip: The returned position-pair is perfect for tool-tips to give them a little arrow!


  • nanopop.version - Current version.

These are static default-values used in case you're not specifying something else:

  • NanoPop.defaultVariantFlipOrder - Default variantFlipOrder values.
  • NanoPop.defaultPositionFlipOrder - Default positionFlipOrder.


  1. The popper-element must have position set to fixed.
  2. window is (currently) the only bounding-element supported.
  3. The library does not perform any automatic updates if the window gets resized, or the user scrolls, so you have to take care of that yourself and call update() in the case.
  4. You might have to fiddle around with z-index to make it work inside of complex, nested, scrollable containers.
You can’t perform that action at this time.