pop-perfect
v1.0.2
Published
Tiny (~1.8KB), zero-dependency positioning engine for tooltips, popovers, and dropdowns. Supports React, Vue, Svelte, Solid, and Angular
Maintainers
Readme
PopPerfect 💎
Stop fighting the DOM. Start shipping UI. The zero-dependency geometry engine that positions your tooltips, popups, and dropdowns perfectly. Every. Single. Time.
Why? 🤔
You use getBoundingClientRect() to position elements. You are breaking your mobile UI.
Native positioning fails in these common scenarios:
- ❌ Mobile Keyboards: The virtual keyboard pushes the viewport, covering your popup.
- ❌ Clipping: Parent containers with
overflow: hiddencut off your tooltips. - ❌ Edge Collisions: Menus open off-screen on smaller devices.
- ❌ Scroll Chaos: Nested scrollable containers break coordinate calculations.
PopPerfect solves this. It combines ResizeObserver, Visual Viewport API, and GPU-accelerated transforms to guarantee perfect placement.
- Universal: First-class support for React, Vue, Svelte, Solid, Angular, and Vanilla.
- Tiny: ~1.8KB gzipped.
- Smart: Uses
requestAnimationFrameto prevent main-thread blocking.
Installation 📦
npm install pop-perfect
# or
pnpm add pop-perfect
# or
yarn add pop-perfectUsage 🚀
React
Use the usePopPosition hook.
import { usePopPosition } from 'pop-perfect/react'
const Tooltip = ({ triggerRef }) => {
// Returns a ref to attach and styles to apply
const { ref, style } = usePopPosition(triggerRef, {
placement: 'top',
offset: 10
})
return <div ref={ref} style={style}>I am perfect!</div>
}Vue 3
Use the usePopPosition composable.
<script setup>
import { ref } from 'vue'
import { usePopPosition } from 'pop-perfect/vue'
const trigger = ref(null)
const { popupRef, style } = usePopPosition(trigger, { placement: 'bottom' })
</script>
<template>
<button ref="trigger">Hover me</button>
<div ref="popupRef" :style="style">
Vue Magic ✨
</div>
</template>
Svelte
Use the popPerfect action.
<script>
import { popPerfect } from 'pop-perfect/svelte'
let trigger;
</script>
<button bind:this={trigger}>Click me</button>
<div use:popPerfect={{ trigger, placement: 'right', flip: true }}>
Svelte Action!
</div>
SolidJS
Use the popPerfect directive.
iimport { createSignal } from 'solid-js';
import { popPerfect } from 'pop-perfect/solid';
// Typescript: declare module 'solid-js' { namespace JSX { interface Directives { popPerfect: any; } } }
function App() {
const [trigger, setTrigger] = createSignal<HTMLElement>();
const [show, setShow] = createSignal(false);
return (
<>
<button ref={setTrigger} onClick={() => setShow(!show())}>Toggle</button>
<Show when={show()}>
<div use:popPerfect={{ trigger: trigger(), placement: 'bottom' }}>
Solid Reactivity ⚡️
</div>
</Show>
</>
);
}
Angular (14+)
Use the standalone PopPerfectDirective.
import { Component } from '@angular/core';
import { PopPerfectDirective } from 'pop-perfect/angular';
@Component({
selector: 'app-dropdown',
standalone: true,
imports: [PopPerfectDirective],
template: `
<button #btn>Open</button>
<div
*ngIf="isOpen"
[popPerfect]="btn"
[popOptions]="{ placement: 'bottom-start', offset: 8 }"
>
Angular Power 🛡️
</div>
`
})
export class DropdownComponent {
isOpen = true;
}
Vanilla JS
import { PopEngine } from 'pop-perfect'
const trigger = document.querySelector('#btn')
const popup = document.querySelector('#tooltip')
const engine = new PopEngine(trigger, popup, {
placement: 'top',
flip: true
})
// Later
// engine.destroy()Configuration ⚙️
You can customize the behavior of the positioning engine.
// React example
usePopPosition(triggerRef, {
placement: 'bottom-end',
offset: 12,
flip: true,
strategy: 'fixed'
})| Feature | PopPerfect 💎 | Native getBoundingClientRect | Heavy Libs (Popper/Floating) |
| :--- | :---: | :---: | :---: |
| Size (Gzipped) | ~1.8kb | 0kb | ~6kb - 20kb |
| Edge Cases | Handled (100+) | You handle them manually 💀 | Handled |
| Setup Time | 30 seconds | Hours of debugging | Moderate |
| Performance | RAF Optimized | Manual optimization needed | Good |
| DX | Hook/Directive | Imperative spaghetti | Configuration heavy |
How it works 🧊
PopPerfect uses a "Reactive Geometry" architecture to keep performance high:
- The Watcher: It uses
ResizeObserveron both the trigger and the popup. If content changes size, the position updates instantly. - Mobile Guard: It listens to the
VisualViewportAPI. When an iOS keyboard slides up, PopPerfect adjusts coordinates so your UI isn't buried. - The Math: It calculates collisions with window edges. If
flip: trueis set, it detects if 'top' will be clipped and seamlessly switches to 'bottom'. - GPU Rendering: Coordinates are applied using
transform: translate3d(...). This promotes the element to its own composite layer, ensuring 60 FPS animations without layout thrashing.
Support the project ❤️
"We eliminated the manual
getBoundingClientRectmath, saved your mobile users from keyboards covering their inputs, and absorbed theVisualViewportAPI nightmare. You saved dozens of hours not reinventing a positioning engine that would have broken inside nested scroll containers anyway. Your donation is a fair trade for pixel-perfect UI and weekends free from math debugging."
If this library saved you time, please consider supporting the development:
- Fiat (Cards/PayPal): via Boosty (one-time or monthly).
- Crypto (USDT/TON/BTC/ETH): view wallet addresses on Telegram.
License
MIT
Keywords
tooltip popover dropdown menu positioning floating anchor overlay react vue svelte solid angular typescript headless ui
