Tooltip and popover positioning engine that automatically calculates optimal placement for UI elements with advanced positioning logic and overflow prevention
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Nine built-in modifiers that control positioning behavior including automatic flipping, overflow prevention, offsetting, arrow positioning, and style application.
Flips the popper to the opposite placement when it would overflow the boundary.
interface FlipOptions {
/** Check overflow on main axis */
mainAxis: boolean;
/** Check overflow on alternate axis */
altAxis: boolean;
/** Custom fallback placements to try in order */
fallbackPlacements: Array<Placement>;
/** Padding around boundary for overflow detection */
padding: Padding;
/** Boundary element(s) or boundary type */
boundary: Boundary;
/** Root boundary for overflow checking */
rootBoundary: RootBoundary;
/** Use alternate boundary context */
altBoundary: boolean;
/** Allow flipping to variation placements */
flipVariations: boolean;
/** Allowed auto placements when using 'auto' */
allowedAutoPlacements: Array<Placement>;
}
type Boundary = Element | Array<Element> | 'clippingParents';
type RootBoundary = 'viewport' | 'document';
type Padding = number | { top?: number; right?: number; bottom?: number; left?: number };Usage Examples:
import { createPopper } from '@popperjs/core';
// Enable flip with default options
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'flip',
enabled: true,
},
],
});
// Custom flip configuration
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'flip',
options: {
fallbackPlacements: ['top', 'right', 'bottom'],
boundary: document.querySelector('.container'),
padding: 5,
},
},
],
});
// Disable main axis flipping but allow alt axis
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'flip',
options: {
mainAxis: false,
altAxis: true,
},
},
],
});Prevents the popper from overflowing its boundary by adjusting its position.
interface PreventOverflowOptions {
/** Prevent overflow on main axis */
mainAxis: boolean;
/** Prevent overflow on alternate axis */
altAxis: boolean;
/** Boundary for overflow detection */
boundary: Boundary;
/** Root boundary context */
rootBoundary: RootBoundary;
/** Use alternate boundary context */
altBoundary: boolean;
/** Allow popper to overflow to stay near reference */
tether: boolean;
/** Offset when tether option activates */
tetherOffset: TetherOffset;
/** Padding around boundary */
padding: Padding;
}
type TetherOffset =
| number
| { mainAxis: number; altAxis: number }
| ((args: { popper: Rect; reference: Rect; placement: Placement }) => number | { mainAxis: number; altAxis: number });Usage Examples:
// Basic overflow prevention
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'preventOverflow',
options: {
boundary: 'clippingParents',
padding: 8,
},
},
],
});
// Tethering to keep popper near reference
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'preventOverflow',
options: {
tether: true,
tetherOffset: 10,
},
},
],
});
// Prevent overflow only on main axis
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'preventOverflow',
options: {
mainAxis: true,
altAxis: false,
},
},
],
});Applies offset to the popper position along main and alternate axes.
interface OffsetOptions {
/** Offset specification as array [skidding, distance] or function */
offset: OffsetsFunction | [number?, number?];
}
type OffsetsFunction = (args: {
popper: Rect;
reference: Rect;
placement: Placement;
}) => [number?, number?];Usage Examples:
// Static offset [skidding, distance]
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'offset',
options: {
offset: [0, 8], // 0 skidding, 8px distance
},
},
],
});
// Dynamic offset based on context
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'offset',
options: {
offset: ({ reference, popper, placement }) => {
// Larger offset for larger references
const distance = reference.width > 100 ? 12 : 6;
return [0, distance];
},
},
},
],
});Positions an arrow element to point to the reference element.
interface ArrowOptions {
/** Arrow element or selector */
element: HTMLElement | string | null;
/** Padding around arrow positioning */
padding: Padding | ((args: { popper: Rect; reference: Rect; placement: Placement }) => Padding);
}Usage Examples:
// Arrow with element reference
const arrowElement = tooltip.querySelector('.arrow');
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'arrow',
options: {
element: arrowElement,
padding: 5,
},
},
],
});
// Arrow with CSS selector
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'arrow',
options: {
element: '.arrow',
padding: { left: 10, right: 10 },
},
},
],
});
// Dynamic arrow padding
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'arrow',
options: {
element: '.arrow',
padding: ({ placement }) => placement.startsWith('top') ? 8 : 4,
},
},
],
});Computes CSS styles for positioning the popper element.
interface ComputeStylesOptions {
/** GPU acceleration using transform3d */
gpuAcceleration: boolean;
/** Adaptive positioning that adjusts for device pixel ratio */
adaptive: boolean;
/** Round pixel values to avoid blurry rendering */
roundOffsets: boolean;
}Usage Examples:
// Disable GPU acceleration
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'computeStyles',
options: {
gpuAcceleration: false,
},
},
],
});
// Configure pixel rounding
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'computeStyles',
options: {
roundOffsets: true,
adaptive: false,
},
},
],
});Applies computed styles to the popper and arrow elements.
// applyStyles has no configurable options
// It automatically applies styles from computeStyles modifierUsage Examples:
// applyStyles is included by default in full createPopper
// Disable if you want to handle styles manually
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'applyStyles',
enabled: false,
},
],
});
// Access computed styles from state instead
const styles = popper.state.styles.popper;
Object.assign(tooltip.style, styles);Manages scroll and resize event listeners for automatic position updates.
interface EventListenersOptions {
/** Listen for scroll events */
scroll: boolean;
/** Listen for resize events */
resize: boolean;
}Usage Examples:
// Disable automatic resize handling
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'eventListeners',
options: {
resize: false,
scroll: true,
},
},
],
});
// Disable all automatic event handling
const popper = createPopper(button, tooltip, {
modifiers: [
{
name: 'eventListeners',
enabled: false,
},
],
});Detects when the reference element is not visible and hides the popper.
// hide modifier has no configurable options
// It sets data-popper-reference-hidden and data-popper-escaped attributesUsage Examples:
// hide modifier is included by default
// Access visibility state from modifiersData
const hideData = popper.state.modifiersData.hide;
if (hideData?.isReferenceHidden) {
console.log('Reference element is not visible');
}
// Style based on visibility attributes
const css = `
[data-popper-reference-hidden] {
visibility: hidden;
pointer-events: none;
}
[data-popper-escaped] {
opacity: 0.5;
}
`;Computes the basic positioning offsets for the popper element.
// popperOffsets modifier has no configurable options
// It computes the base x, y coordinates for popper positioningUsage Examples:
// popperOffsets is a core modifier required by most others
// Access computed offsets from state
const offsets = popper.state.modifiersData.popperOffsets;
console.log('Popper position:', offsets.x, offsets.y);
// This modifier should not be disabled as it's foundationalStructure that all modifiers must follow.
interface Modifier<Name, Options> {
/** Unique name identifier */
name: Name;
/** Whether the modifier is enabled */
enabled: boolean;
/** Execution phase during update cycle */
phase: ModifierPhases;
/** Required modifier names that must run before this one */
requires?: Array<string>;
/** Optional modifier names that should run before if present */
requiresIfExists?: Array<string>;
/** Main modifier function executed during updates */
fn: (args: ModifierArguments<Options>) => State | void;
/** Effect function for setup/cleanup (runs once) */
effect?: (args: ModifierArguments<Options>) => (() => void) | void;
/** Default options for the modifier */
options?: Partial<Options>;
/** Static data for the modifier */
data?: any;
}
interface ModifierArguments<Options> {
state: State;
instance: Instance;
options: Partial<Options>;
name: string;
}
type ModifierPhases =
| 'beforeRead' | 'read' | 'afterRead'
| 'beforeMain' | 'main' | 'afterMain'
| 'beforeWrite' | 'write' | 'afterWrite';Usage Examples:
// Custom modifier example
const customModifier = {
name: 'customOffset',
enabled: true,
phase: 'main',
requires: ['popperOffsets'],
fn({ state }) {
// Apply custom offset logic
state.modifiersData.popperOffsets.y += 10;
},
};
const popper = createPopper(button, tooltip, {
modifiers: [customModifier],
});Install with Tessl CLI
npx tessl i tessl/npm-popperjs--core