[Skip to main content](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026#main-content) # [Floating UI vs Tippy.js vs Radix Tooltip: Popover Positioning 2026](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#floating-ui-vs-tippyjs-vs-radix-tooltip-popover-positioning-2026) ## [TL;DR](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#tldr) Building tooltips, popovers, dropdowns, and floating menus correctly is deceptively hard — viewport overflow, collision detection, scroll containers, and keyboard accessibility are all gotchas that custom solutions routinely miss. **Floating UI** (successor to Popper.js from the same authors) is the low-level positioning engine — pure geometry and collision detection, totally unstyled, works with any framework, and is what Radix, Mantine, and many others use internally. **Tippy.js** is the batteries-included tooltip library built on Popper.js — styled out of the box, declarative API, animates, works in vanilla JS and React — but it's showing its age in 2026 with no App Router support and weaker accessibility guarantees. **Radix UI's Tooltip and Popover** are headless, fully accessible (WAI-ARIA compliant), React-only components built on Floating UI internally — the correct choice for React/Next.js component libraries where accessibility is non-negotiable. For low-level control over positioning in any framework: Floating UI. For quick tooltips with minimal config: Tippy.js. For production React UIs that must be accessible: Radix Tooltip/Popover. ## [Key Takeaways](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#key-takeaways) - **Floating UI is framework-agnostic** — core is vanilla JS, `@floating-ui/react` adds React hooks - **Floating UI handles all edge cases** — viewport overflow, flip, shift, arrow, virtual elements - **Tippy.js is easiest to get started** — `` wraps any element - **Radix Tooltip is fully WAI-ARIA compliant** — focus management, screen readers, keyboard nav - **Tippy.js is built on Popper.js** — Floating UI's predecessor, still maintained but less active - **Radix Popover manages open state** — controlled and uncontrolled modes, portal rendering - **Floating UI powers Radix internally** — Radix uses `@floating-ui/react-dom` under the hood * * * ## [Use Case Map](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#use-case-map) ``` Simple tooltip on hover → Tippy.js or Radix Tooltip Tooltip with custom render → Floating UI or Radix Tooltip Accessible popover with content → Radix Popover Dropdown menu with keyboard nav → Radix DropdownMenu Custom positioning engine → Floating UI (raw) Framework-agnostic tooltip → Tippy.js or Floating UI Select/Combobox overlay → Floating UI or Radix Select Context menu (right-click) → Radix ContextMenu ``` * * * ## [Floating UI: The Positioning Engine](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#floating-ui-the-positioning-engine) Floating UI provides the geometry and collision detection algorithms — you wire up the DOM refs and React state yourself. ### [Installation](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#installation) ```bash npm install @floating-ui/react # For vanilla JS (no React): npm install @floating-ui/dom ``` ### [Basic Tooltip](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#basic-tooltip) ```tsx import { useFloating, autoUpdate, offset, flip, shift, useHover, useFocus, useDismiss, useRole, useInteractions, FloatingPortal, } from "@floating-ui/react"; import { useState } from "react"; interface TooltipProps { content: string; children: React.ReactElement; } export function Tooltip({ content, children }: TooltipProps) { const [isOpen, setIsOpen] = useState(false); const { refs, floatingStyles, context } = useFloating({ open: isOpen, onOpenChange: setIsOpen, placement: "top", // Keep in sync with scroll and resize whileElementsMounted: autoUpdate, middleware: [\ offset(8), // Distance from reference\ flip(), // Flip to bottom if no space above\ shift({ padding: 8 }), // Shift horizontally to stay in viewport\ ], }); // Interaction hooks — compose behaviors const hover = useHover(context, { move: false }); const focus = useFocus(context); const dismiss = useDismiss(context); const role = useRole(context, { role: "tooltip" }); const { getReferenceProps, getFloatingProps } = useInteractions([\ hover,\ focus,\ dismiss,\ role,\ ]); return ( <> {/* Attach to trigger element */} {React.cloneElement(children, { ref: refs.setReference, ...getReferenceProps(), })} {/* Tooltip — rendered in portal to escape stacking contexts */} {isOpen && (
{content}
)}
); } // Usage ``` ### [Arrow Placement](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#arrow-placement) ```tsx import { useFloating, arrow, offset, flip, FloatingArrow, } from "@floating-ui/react"; import { useRef } from "react"; export function TooltipWithArrow({ content, children }: TooltipProps) { const arrowRef = useRef(null); const { refs, floatingStyles, context, middlewareData, placement } = useFloating({ middleware: [\ offset(10),\ flip(),\ arrow({ element: arrowRef }),\ ], }); return ( <>
{children}
{content} {/* FloatingArrow renders an SVG arrow positioned correctly */}
); } ``` ### [Popover (Click-to-Open)](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#popover-click-to-open) ```tsx import { useFloating, autoUpdate, offset, flip, shift, useClick, useDismiss, useRole, useInteractions, FloatingPortal, FloatingFocusManager, } from "@floating-ui/react"; import { useState } from "react"; export function Popover({ trigger, content }: { trigger: React.ReactNode; content: React.ReactNode }) { const [isOpen, setIsOpen] = useState(false); const { refs, floatingStyles, context } = useFloating({ open: isOpen, onOpenChange: setIsOpen, placement: "bottom-start", whileElementsMounted: autoUpdate, middleware: [offset(4), flip(), shift({ padding: 8 })], }); const click = useClick(context); const dismiss = useDismiss(context); const role = useRole(context, { role: "dialog" }); const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss, role]); return ( <>
{trigger}
{isOpen && ( // FloatingFocusManager traps focus inside the popover
{content}
)}
); } ``` ### [Virtual Element (Context Menu)](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#virtual-element-context-menu) ```tsx import { useFloating, offset, flip, shift, useClientPoint, useInteractions } from "@floating-ui/react"; import { useState } from "react"; export function ContextMenu({ items }: { items: string[] }) { const [isOpen, setIsOpen] = useState(false); const { refs, floatingStyles, context } = useFloating({ open: isOpen, onOpenChange: setIsOpen, placement: "bottom-start", middleware: [offset({ mainAxis: 5, alignmentAxis: 4 }), flip(), shift()], }); // Follow the mouse cursor const clientPoint = useClientPoint(context); const { getReferenceProps, getFloatingProps } = useInteractions([clientPoint]); return (
{ e.preventDefault(); setIsOpen(true); }} style={{ minHeight: 200, border: "1px dashed #ccc", padding: 16 }} {...getReferenceProps()} > Right-click anywhere here {isOpen && (
{items.map((item) => ( ))}
)}
); } ``` * * * ## [Tippy.js: Batteries-Included Tooltips](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#tippyjs-batteries-included-tooltips) Tippy.js provides a complete tooltip and popover solution with themes, animations, and a declarative API — minimal configuration required. ### [Installation](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#installation-1) ```bash npm install tippy.js @tippyjs/react ``` ### [Basic Usage](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#basic-usage) ```tsx import Tippy from "@tippyjs/react"; import "tippy.js/dist/tippy.css"; // Default theme export function CopyButton() { return ( ); } ``` ### [Placement and Options](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#placement-and-options) ```tsx import Tippy from "@tippyjs/react"; export function FeatureTooltips() { return (
{/* Delay: 300ms show, 100ms hide */} {/* Click to toggle instead of hover */} {/* Interactive (won't close when hovering tooltip) */} Rich content

With multiple elements

Read more
} interactive interactiveBorder={20} placement="bottom" >
{/* Disabled */} ); } ``` ### [Animations and Themes](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#animations-and-themes) ```tsx import Tippy from "@tippyjs/react"; import "tippy.js/dist/tippy.css"; import "tippy.js/animations/scale.css"; import "tippy.js/themes/light.css"; import "tippy.js/themes/material.css"; export function ThemedTooltips() { return ( <> {/* Built-in light theme */} {/* Scale animation */} {/* Custom theme via CSS */} ); } ``` ### [Controlled Tippy](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#controlled-tippy) ```tsx import Tippy from "@tippyjs/react"; import { useState } from "react"; export function ControlledTooltip() { const [visible, setVisible] = useState(false); return ( setVisible(false)} interactive > ); } ``` * * * ## [Radix UI Tooltip and Popover: Accessible Components](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#radix-ui-tooltip-and-popover-accessible-components) Radix provides fully accessible, headless components with correct ARIA roles, focus management, and keyboard navigation — you bring your own styles. ### [Installation](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#installation-2) ```bash npm install @radix-ui/react-tooltip @radix-ui/react-popover ``` ### [Tooltip](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#tooltip) ```tsx import * as Tooltip from "@radix-ui/react-tooltip"; // Provider wraps your app — controls delay behavior globally export function App() { return ( ); } // Individual tooltip export function DeleteButton() { return ( Delete item ); } // CSS /* .tooltip-content { background: #1a1a1a; color: white; border-radius: 4px; padding: 4px 10px; font-size: 13px; box-shadow: 0 2px 8px rgba(0,0,0,0.2); animation-duration: 150ms; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); will-change: transform, opacity; } .tooltip-content[data-state='delayed-open'][data-side='top'] { animation-name: slideDownAndFade; } @keyframes slideDownAndFade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } } .tooltip-arrow { fill: #1a1a1a; } */ ``` ### [Popover](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#popover) ```tsx import * as Popover from "@radix-ui/react-popover"; export function FilterPopover() { return ( e.preventDefault()} >

Filter Options

); } ``` ### [Tooltip with Tailwind (shadcn/ui Pattern)](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#tooltip-with-tailwind-shadcnui-pattern) ```tsx // components/ui/tooltip.tsx — shadcn/ui Tooltip component import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import { cn } from "@/lib/utils"; const TooltipProvider = TooltipPrimitive.Provider; const TooltipRoot = TooltipPrimitive.Root; const TooltipTrigger = TooltipPrimitive.Trigger; const TooltipContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, sideOffset = 4, ...props }, ref) => ( )); TooltipContent.displayName = TooltipPrimitive.Content.displayName; // Export the Tooltip component export function Tooltip({ children, content, ...props }: { children: React.ReactNode; content: React.ReactNode; } & React.ComponentPropsWithoutRef) { return ( {children} {content} ); } // Usage with Tailwind ``` * * * ## [Feature Comparison](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#feature-comparison) | Feature | Floating UI | Tippy.js | Radix Tooltip/Popover | | --- | --- | --- | --- | | **Framework** | Any (React, Vue, Svelte) | Any + React | React only | | **Styling** | Unstyled (bring your own) | Styled (override available) | Unstyled (bring your own) | | **Accessibility** | Manual (you implement) | Basic | ✅ WAI-ARIA compliant | | **Focus trap** | `FloatingFocusManager` | No | ✅ Built-in | | **Keyboard nav** | Via hooks | Basic | ✅ Built-in | | **Collision detection** | ✅ Advanced | ✅ Via Popper.js | ✅ Via Floating UI | | **Arrow positioning** | ✅ `FloatingArrow` | ✅ Built-in | ✅ `Tooltip.Arrow` | | **Animations** | CSS (you define) | ✅ Built-in themes | CSS data-state | | **Portal** | ✅ `FloatingPortal` | ✅ Auto | ✅ `Portal` | | **Virtual elements** | ✅ | Limited | No | | **Bundle size** | ~10kB | ~15kB | ~8kB per primitive | | **npm weekly** | 12M | 3M | 8M (tooltip) | | **GitHub stars** | 29k | 11k | 22k (radix-ui/primitives) | | **TypeScript** | ✅ Full | ✅ | ✅ Full | * * * ## [When to Use Each](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#when-to-use-each) **Choose Floating UI if:** - Building a component library from scratch (unstyled primitives) - Need maximum control over positioning behavior and styling - Framework-agnostic — Vue, Svelte, vanilla JS, or React - Virtual element positioning (context menus, cursors) - Complex middleware requirements (custom offset logic) - Want to understand exactly what's happening — no magic **Choose Tippy.js if:** - Quick tooltip needed with minimal setup - Vanilla JS project or legacy codebase - Want built-in themes and animations without CSS work - Simple hover tooltips where accessibility is secondary - Prototyping or internal tools where ARIA isn't critical **Choose Radix Tooltip/Popover if:** - React/Next.js production application - Accessibility is required — screen readers, keyboard navigation - Using shadcn/ui (Radix is the foundation) - Want compound component API with proper focus management - Need `asChild` pattern to avoid extra DOM elements - Building a design system where consumers control all styling * * * ## [Methodology](https://www.pkgpulse.com/blog/floating-ui-vs-tippyjs-vs-radix-tooltip-popover-2026\#methodology) Data sourced from Floating UI documentation (floating-ui.com/docs), Tippy.js documentation (atomiks.github.io/tippyjs), Radix UI documentation (radix-ui.com/docs), npm weekly download statistics as of February 2026, GitHub star counts as of February 2026, and community discussions from the React Discord, CSS-Tricks, and web accessibility forums. * * * _Related: [Radix UI vs Headless UI vs Ariakit](https://www.pkgpulse.com/blog/radix-ui-vs-headless-ui-vs-ariakit-accessible-react-components-2026) for broader headless component comparisons, or [shadcn/ui vs Mantine vs Chakra UI](https://www.pkgpulse.com/blog/shadcn-vs-mantine-vs-chakra-react-component-library-2026) for styled React component libraries._ ## Comments ### The 2026 JavaScript Stack Cheatsheet One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly. Get the Free Cheatsheet