A tiny JS/TS library for positioning floating UI elements (tooltips, popovers, menus).
npm i @codemonster-ru/floater.jsimport { computePosition, offset, shift, flip, arrow, autoUpdate } from '@codemonster-ru/floater.js';
const reference = document.querySelector('#reference') as HTMLElement;
const floating = document.querySelector('#floating') as HTMLElement;
const arrowEl = document.querySelector('#arrow') as HTMLElement;
const update = () => {
computePosition(reference, floating, {
placement: 'right',
middleware: [shift(), offset(8), arrow(arrowEl)],
}).then(({ x, y, middlewareData }) => {
floating.style.left = `${x}px`;
floating.style.top = `${y}px`;
if (middlewareData.arrow) {
arrowEl.style.left = `${middlewareData.arrow.x}px`;
arrowEl.style.top = `${middlewareData.arrow.y}px`;
}
});
};
const cleanup = autoUpdate(reference, update);
update();
// Later: cleanup();Returns a Promise that resolves to { x, y, placement, middlewareData }.
reference:HTMLElement | VirtualElementfloating:HTMLElementoptions.placement: one ofplacementTypes(default:bottom)options.middleware: array of middlewareoptions.strategy:'absolute' | 'fixed'(default:'absolute')
- With
strategy: 'absolute',xandyare coordinates in the floating element's offset parent coordinate space (suitable forstyle.left/top). - With
strategy: 'fixed',xandyare viewport coordinates (suitable forposition: fixedelements teleported tobody). - Middleware runs in the order provided and can change
x,y, andplacement. middlewareData[name]stores the final result returned by that middleware.- When
arrow(...)is used,middlewareData.arrowincludes:x/y: arrow coordinates relative to the floating elementbaseX/baseY: floating coordinates used for arrow calculation
// floating.style.position = 'fixed'
// document.body.appendChild(floating)
computePosition(reference, floating, {
placement: 'bottom',
strategy: 'fixed',
middleware: [offset(8), flip({ placements: ['bottom', 'top'] }), shift()],
}).then(({ x, y }) => {
floating.style.left = `${x}px`;
floating.style.top = `${y}px`;
});Array of supported placements:
top, top-start, top-end,
right, right-start, right-end,
bottom, bottom-start, bottom-end,
left, left-start, left-end
Offsets the floating element from the reference by value pixels.
Keeps the floating element inside the visible area.
params.parent: optional container element. If provided, clamping uses that container; otherwise it uses the scroll parent.
If the placement is not visible, tries other placements.
When used together with shift(), the fit check ignores shift() to avoid picking placements that only fit after shifting.
params.placements: optional list of placements to try, in order. Useful to restrict flipping (e.g. onlytop/bottom).
Example: restrict flipping to vertical directions only.
computePosition(reference, floating, {
placement: 'bottom',
middleware: [offset(8), flip({ placements: ['bottom', 'top'] }), shift()],
});Computes arrow position and exposes it through middlewareData.arrow.
middlewareData.arrow.x/y: arrow coordinates relative to the floating elementmiddlewareData.arrow.baseX/baseY: floating coordinates used for arrow calculation
Watches scroll/resize and calls callback.
Returns a cleanup function to remove listeners.
- Listeners are attached to:
- the nearest scroll parent (if found)
windowscrollwindowresizeResizeObserverforreferencewhen available
- The returned cleanup function removes all listeners/observers added by
autoUpdate.
Use when you need a virtual reference (e.g. mouse position).
const virtualEl: VirtualElement = {
offsetTop: 100,
offsetLeft: 200,
getBoundingClientRect() {
return {
x: 200,
y: 100,
width: 0,
height: 0,
top: 100,
right: 200,
bottom: 100,
left: 200,
};
},
};The package ships with types. See index.ts for full exports.
autoUpdate(...)now returns a cleanup function. Call it when the floating UI unmounts.middlewareData.arrownow keeps arrow coordinates (x/y) and also exposesbaseX/baseY.- Positioning and clamping behavior was hardened for scroll containers, viewport checks, and
shift + offsetinteractions.