Back to Blog
Floating UI: Stop Fighting the DOM for Overlay Positioning
7 min readJun 18, 20269 views

Floating UI: Stop Fighting the DOM for Overlay Positioning

Getting tooltips, dropdowns, and popovers to stay where they belong, especially when scrolling or resizing, is a nightmare. Floating UI is the library that solves this once and for all, with a tiny footprint and robust positioning logic.

FrontendWeb DevelopmentToolingUI
Share

by Sunil Band

The Perennial Pain of Overlay Positioning

If you've built anything beyond a static marketing page, you've probably wrestled with positioning overlays. Tooltips, dropdowns, popovers, context menus – they all seem deceptively simple until you hit the real world. Suddenly, they're clipping off-screen, jumping around on scroll, or refusing to align correctly when their content changes size. You reach for position: absolute or fixed, maybe even transform: translate, and quickly find yourself trapped in a brittle CSS maze, battling z-index wars and calculating offsets in JavaScript.

I've been there countless times. My first attempts usually involve getBoundingClientRect() and some manual math, which inevitably breaks when the viewport resizes, the page scrolls, or the content inside the overlay changes. It's a never-ending game of whack-a-mole, and frankly, it's a huge waste of engineering time for something that feels like it should be simple. This is exactly the problem Floating UI solves, and it does so with elegance and efficiency.

Why Floating UI is a Game Changer

Floating UI isn't just another utility library; it's a fundamental shift in how we approach positioning dynamic elements. It abstracts away all the complexity of calculating positions, detecting collisions, flipping, shifting, and even managing the dreaded scroll event. Before Floating UI, you either rolled your own brittle solution or pulled in a massive UI library just for its positioning engine. Floating UI gives you that core, robust engine without the rest of the baggage.

The library operates on two core concepts: a floating element (your tooltip, popover, etc.) and a reference element (the button, input, or icon it's attached to). You tell Floating UI which element is which, define your desired placement (e.g., 'top', 'bottom-start'), and it handles the rest. It's framework-agnostic, tiny (under 5kb), and incredibly performant.

Getting Started with Floating UI

Let's walk through a common example: a simple tooltip. We want the tooltip to appear below a button, but if there's not enough space, it should flip to the top. It should also stay with the button even if the page scrolls.

First, install it:

bash
npm install @floating-ui/react
# or for vanilla JS
npm install @floating-ui/dom

I'll use the @floating-ui/react package for this example, as it provides convenient hooks that integrate seamlessly with React's lifecycle. However, the core logic is identical for the vanilla @floating-ui/dom package.

typescript
import React, { useState, useRef } from 'react';
import { useFloating, useHover, useFocus, useDismiss, useRole, useInteractions, offset, flip, shift, autoUpdate } from '@floating-ui/react';

interface TooltipProps {
  content: string;
  children: React.ReactNode;
}

function Tooltip({ content, children }: TooltipProps) {
  const [isOpen, setIsOpen] = useState(false);

  const { refs, floatingStyles, context } = useFloating({
    open: isOpen,
    onOpenChange: setIsOpen,
    placement: 'bottom-start', // Initial placement: bottom, aligned to the start
    whileElementsMounted: autoUpdate, // Keep updating position while elements are mounted
    middleware: [
      offset(8), // Keep 8px distance from the reference element
      flip(),    // If bottom doesn't fit, flip to top, then left/right
      shift(),   // If still not fitting, shift along the available axis
    ],
  });

  const hover = useHover(context, { move: false }); // Show/hide on hover
  const focus = useFocus(context); // Show/hide on focus
  const dismiss = useDismiss(context); // Hide on escape key or outside click
  const role = useRole(context, { role: 'tooltip' }); // Accessibility role

  const { getReferenceProps, getFloatingProps } = useInteractions([
    hover,
    focus,
    dismiss,
    role,
  ]);

  return (
    <>
      {/* This is our reference element. The tooltip will position relative to it. */}
      <span ref={refs.setReference} {...getReferenceProps()}>
        {children}
      </span>
      {isOpen && (
        <div
          ref={refs.setFloating}
          style={floatingStyles}
          {...getFloatingProps()}
          className="tooltip"
        >
          {content}
        </div>
      )}
      <style jsx>{`
        .tooltip {
          background: #333;
          color: #fff;
          padding: 8px 12px;
          border-radius: 4px;
          font-size: 14px;
          z-index: 1000;
          box-shadow: 0 2px 10px rgba(0,0,0,0.2);
        }
      `}</style>
    </>
  );
}

export default function App() {
  return (
    <div style={{ padding: '150px', display: 'flex', gap: '20px', justifyContent: 'center', alignItems: 'center', height: '100vh', flexDirection: 'column' }}>
      <h1>Floating UI Demo</h1>
      <Tooltip content="Hello, I'm a tooltip!">
        <button>Hover me</button>
      </Tooltip>
      <div style={{ height: '500px', width: '1px', background: '#ccc' }}></div> {/* Spacer to force scroll */}
      <Tooltip content="I'm another tooltip, try scrolling!">
        <button>Focus me</button>
      </Tooltip>
    </div>
  );
}

Let's break down what's happening:

  1. useFloating: This is the core hook. We pass it open and onOpenChange to control the tooltip's visibility. placement: 'bottom-start' tells Floating UI to try and place the floating element below the reference, aligned to its start.
  2. whileElementsMounted: autoUpdate: This is crucial. It automatically re-positions the tooltip whenever the reference or floating element moves, resizes, or the page scrolls. No more manual event listeners!
  3. middleware: This is where the magic happens. We define an array of functions that Floating UI runs to determine the final position:
    • offset(8): Adds 8 pixels of spacing between the button and the tooltip.
    • flip(): If the initial bottom-start placement causes the tooltip to go off-screen, it tries top-start, then bottom-end, top-end, and so on, until it finds a placement that fits. This is powerful for responsive UIs.
    • shift(): If flip still can't find a perfect fit, shift will slide the tooltip along its current axis to keep it entirely within the viewport. This prevents clipping even in tight spaces.
  1. useInteractions: This hook combines various interaction hooks like useHover, useFocus, useDismiss, and useRole into a single, cohesive API. This handles common tooltip behaviors and ensures accessibility attributes are correctly applied.
  2. refs.setReference and refs.setFloating: These are ref setters that Floating UI uses to get references to your DOM elements. This is how it knows what to position relative to what.
  3. floatingStyles: This object contains the left, top, position, and transform CSS properties calculated by Floating UI. You apply these directly to your floating element.

With this setup, you get a robust tooltip that automatically adapts to its environment, stays within the viewport, and handles common user interactions out of the box. The amount of boilerplate saved, and the peace of mind knowing it just works, is immense.

More Complex Scenarios

Floating UI isn't limited to simple tooltips. It excels in more complex scenarios like:

  • Dropdown menus: You might want a dropdown to open directly below a button but expand upwards if there's not enough space. flip handles this naturally.
  • Context menus: These often appear at the mouse cursor position. Floating UI can take arbitrary coordinates as a reference, making this trivial.
  • Modals or popovers within scrollable containers: If your reference element is inside a div with overflow: auto, Floating UI still tracks it correctly.
  • Nested overlays: A tooltip on an item within a dropdown menu? No problem. The library is designed to handle multiple layers of floating elements.

The middleware array is where you customize behavior. Beyond offset, flip, and shift, there are other useful options like arrow (to position a tooltip arrow), hide (to detect if the reference is obscured), and size (to adjust the floating element's size to fit).

The Trade-Offs

While Floating UI solves a significant pain point, it's important to acknowledge its place in the ecosystem. It's a low-level primitive for positioning. It doesn't provide any UI components or styling. You're still responsible for the look and feel of your overlays and managing their open/closed state.

This is by design, and frankly, it's a strength. It means you can integrate Floating UI into any framework, with any design system, without fighting opinionated components. However, if you're looking for a complete, batteries-included solution for all your UI components, you'll still need a library like Material UI or Radix UI (which, incidentally, uses Floating UI under the hood for its positioning). Floating UI is the foundational layer, not the entire house.

Another minor consideration is the initial flash of unstyled content. Since floatingStyles are applied after rendering, there can be a brief moment where the floating element is in its default position before JavaScript corrects it. This is usually imperceptible for small, fast-opening elements, but for larger, slower elements, you might want to hide the floating element initially with visibility: hidden and then make it visible after the first position calculation.

Wrapping up

Stop writing custom positioning logic. Seriously. It's a rabbit hole that never ends well. Floating UI is the industry standard for a reason – it's robust, tested, and handles all the edge cases you'd rather not think about. It's the kind of library that makes you forget you ever had a problem in the first place.

If you've been battling position: absolute and getBoundingClientRect() for your overlays, give Floating UI a try. Clone the example above, or better yet, head over to the Floating UI documentation and experiment with their interactive demos. You'll quickly see how it simplifies complex positioning scenarios and frees you up to focus on the actual functionality of your application.

More from the blog
Available for projectsReady to make something fun 🎈

Ready to build the next system?Wanna build something awesome together?

Currently accepting high-impact opportunities in frontend engineering and scalable web applications.Got a cool idea rattling around? Let's grab a virtual coffee and turn it into something people love. ☕