Floating UI — A battle-tested, accessible positioning engine for tooltips, popovers, dropdowns, and other floating elements
Floating UI is a lightweight, TypeScript-first library that solves the notoriously hard problem of correctly positioning and interacting with floating UI elements—while guaranteeing visibility, accessibility, and cross-platform flexibility.
Floating UI (formerly Popper.js) is the de facto standard positioning engine for modern front-end applications—and for good reason. With 32,625 GitHub stars, an MIT license, and deep roots in production-grade UI libraries (including Material UI, Chakra UI, Radix UI, and Mantine), it’s not just popular—it’s trusted. For React developers building anything that floats—a tooltip on hover, a context menu on right-click, a combobox dropdown, or a mobile-friendly bottom sheet—Floating UI removes the guesswork, edge-case fatigue, and accessibility debt that usually comes with position: absolute.
The Pain It Solves
Positioning floating elements seems simple until you confront reality:
- A tooltip anchored to a button disappears when scrolled into a clipped container.
- A popover renders off-screen near the viewport edge and isn’t repositioned automatically.
- Keyboard navigation (e.g.,
Tab,Esc,ArrowDown) isn’t wired up consistently across devices or screen readers. - You end up writing fragile
getBoundingClientRect()logic, manual collision detection, or brittleuseEffect-based repositioning—and still fail WCAG 2.1 success criteria like “Focus Visible” or “Keyboard Trap”.
Floating UI abstracts all of this into a composable, platform-agnostic core—and then layers on React-specific primitives that handle both geometry and interaction out of the box.
Key Features That Matter to React Developers
✅ Smart, adaptive positioning: Uses a pluggable algorithm (computePosition) that auto-flips, shifts, resizes, and repositions based on available space—no manual if (x > window.innerWidth) checks.
✅ Built-in accessibility hooks: useClick, useHover, useDismiss, useRole, useFocus, and useInteractions collectively handle keyboard navigation, focus management, click-outside dismissal, and ARIA attributes (e.g., aria-expanded, role="tooltip").
✅ Zero-runtime bundle size optimization: Choose @floating-ui/react-dom (~2.4 KB gzipped) for positioning-only needs—or @floating-ui/react (~4.8 KB) for full interactivity. Both are tree-shakable and ESM-first.
✅ Cross-platform extensibility: Core logic lives in @floating-ui/core, enabling custom platforms (e.g., Canvas, React Native, or even SSR-rendered static tooltips).
✅ Visual, snapshot-based testing: Every positioning behavior—including nested scrolling, RTL layouts, zoom levels, and dynamic resizing—is verified via Playwright screenshots in their dev playground—giving you confidence before shipping.
Typical Usage: A Fully Accessible Tooltip in <20 Lines
Here’s how you’d build a production-ready tooltip using @floating-ui/react:
import { useState, useRef, useMemo } from 'react';
import {
useFloating,
useClick,
useHover,
useDismiss,
useRole,
useInteractions,
FloatingPortal,
arrow,
offset,
flip,
shift,
} from '@floating-ui/react';
function Tooltip({ children, label }: { children: React.ReactNode; label: string }) {
const [isOpen, setIsOpen] = useState(false);
const arrowRef = useRef<SVGSVGElement>(null);
const { x, y, strategy, refs, context, middlewareData } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
middleware: [
offset(4),
flip(),
shift(),
arrow({ element: arrowRef }),
],
});
const click = useClick(context);
const hover = useHover(context, { move: false, delay: { open: 500 } });
const dismiss = useDismiss(context);
const role = useRole(context, { role: 'tooltip' });
const { getReferenceProps, getFloatingProps, getArrowProps } = useInteractions([
click,
hover,
dismiss,
role,
]);
return (
<>
<div ref={refs.setReference} {...getReferenceProps()}>
{children}
</div>
<FloatingPortal>
{isOpen && (
<div
ref={refs.setFloating}
style={{
position: strategy,
top: y ?? 0,
left: x ?? 0,
zIndex: 9999,
}}
{...getFloatingProps()}
>
<div className="bg-gray-900 text-white text-sm rounded px-2 py-1">
{label}
<div
ref={arrowRef}
style={{
position: 'absolute',
left: middlewareData.arrow?.x ?? 0,
top: middlewareData.arrow?.y ?? 0,
}}
{...getArrowProps()}
>
<svg width="8" height="4" viewBox="0 0 8 4">
<path d="M0 0L4 4L8 0" fill="currentColor" />
</svg>
</div>
</div>
</div>
)}
</FloatingPortal>
</>
);
}
// Usage
function App() {
return (
<Tooltip label="This is an accessible, keyboard-navigable tooltip">
<button>Hover or focus me</button>
</Tooltip>
);
}This example handles hover and focus-triggered display, automatic repositioning on scroll/resize, arrow alignment, dismiss-on-escape/click-outside, and proper ARIA semantics—all without external dependencies or custom event listeners.
Who It’s For
- React library authors who need a reliable, tested foundation for floating components (dropdowns, menus, selects).
- Product teams shipping design systems, where consistency, accessibility compliance, and visual reliability are non-negotiable.
- Senior front-end engineers tired of maintaining homegrown positioning logic—and ready to adopt a mature, community-vetted solution.
Floating UI doesn’t ship pre-styled components. It ships primitives—so you retain full control over styling, animation, and behavior while delegating the hardest parts: geometry, accessibility, and ergonomics. And with its monorepo architecture, TypeScript-first APIs, and active maintenance (last push: 2026-06-10), it’s built not just to work—but to scale alongside your application’s complexity.
