Skip to content

React DOM

This package exports React bindings for the @floating-ui/dom package — a library that provides “anchored positioning” for a floating element to position it next to a given reference element.

npm install @floating-ui/react-dom

If you want to build interactions for floating elements easily, in addition to the positioning, check out React DOM Interactions.

Usage

useFloating() wraps computePosition() and accepts all of its options. The most basic usage is the following:

import {useFloating} from '@floating-ui/react-dom';
 
function App() {
  const {x, y, reference, floating, strategy} = useFloating();
 
  return (
    <>
      <button ref={reference}>Button</button>
      <div
        ref={floating}
        style={{
          position: strategy,
          top: y ?? 0,
          left: x ?? 0,
        }}
      >
        Tooltip
      </div>
    </>
  );
}

This will position the floating Tooltip element at the bottom center of the Button element by default.

  • x and y are the positioning coordinates. These values are null initially, before the layout effect has fired (i.e. SSR).
  • reference and floating are callback refs, not regular refs. This allows the position to be updated when either elements change (e.g. conditional rendering).
  • strategy is a string, 'absolute' (default) or 'fixed'

To customize the positioning, view the computePosition options. Example:

import {
  useFloating,
  offset,
  flip,
  shift,
} from '@floating-ui/react-dom';
 
useFloating({
  placement: 'right',
  strategy: 'fixed',
  middleware: [offset(10), flip(), shift()],
});

Updating

useFloating() only calculates the position once on render, or when the reference/floating elements changed — for example, the floating element gets mounted via conditional rendering.

If the floating element lives in a different offsetParent context to the reference element, it will need to be updated while mounted to remain “anchored”. This includes scrolling and resizing the window or the elements themselves.

To do so, use the autoUpdate utility:

import {useFloating, autoUpdate} from '@floating-ui/react-dom';
 
function App() {
  useFloating({
    whileElementsMounted: autoUpdate,
 
    // Or, pass options. Ensure the cleanup function is returned.
    whileElementsMounted: (reference, floating, update) =>
      autoUpdate(reference, floating, update, {
        animationFrame: true,
      }),
  });
}

whileElementsMounted is a fully reactive callback prop to handle mounting/unmounting of the elements.

autoUpdate is expensive and should only be called when both the reference and floating elements are mounted. If your floating element is NOT conditionally rendered, and instead relies of visibility for instance, use an effect instead of this prop.

Alternatively (or additionally), you may want to update manually in some cases. The hook returns an update() function to update the position at will:

const {update} = useFloating();

External reference

As reference is a callback ref, you can call it with your external reference element:

function MyComponent({triggerElement}) {
  const {reference} = useFloating();
 
  useLayoutEffect(() => {
    reference(triggerElement);
  }, [triggerElement]);
}

The element should be stored in state rather than a plain ref to ensure reactive updating when it changes.

You may instead want to utilize the children prop as the reference, and attach the ref via cloneElement(). This also co-locates the reference with the floating component.

cloneElement(children, {ref: reference});

Internal refs

As mentioned previously, reference and floating are callback refs to handle conditional rendering. They aren't suitable for usage as regular refs. Instead, access refs:

const {refs} = useFloating();
 
// Accessing them inside effects or event handlers:
refs.floating.current;
refs.reference.current;

If another library's hook requires a ref passed to it, you can do so:

const {refs} = useFloating();
const otherLib = useOtherLib({
  ref: refs.floating, // or refs.reference
});

Arrow

A ref can be passed as the element:

import {useRef} from 'react';
import {useFloating, arrow} from '@floating-ui/react-dom';
 
function App() {
  const arrowRef = useRef(null);
  const {
    x,
    y,
    reference,
    floating,
    middlewareData: {arrow: {x: arrowX, y: arrowY} = {}},
  } = useFloating({
    middleware: [arrow({element: arrowRef})],
  });
 
  return (
    <>
      <button ref={reference}>My button</button>
      <div ref={floating}>
        My tooltip
        <div ref={arrowRef} />
      </div>
    </>
  );
}

Arrow styles

In the tutorial, it shows you how to style the arrow.

This styling relies on the final (or resultant) placement, which can be different from the preferred one you passed as an option to useFloating() (if you're using flip() or autoPlacement() middleware).

This final placement gets returned from the hook to enable the static side position style:

const {placement} = useFloating();

Conditional rendering

In some cases, you might be conditionally rendering the arrow element (not just the floating element). For instance, you're demoing a showArrow={boolean} prop for a component while it's mounted.

Utilize the same technique as the reference and floating elements which is a callback function that calls update() after assigning the ref value.

const arrowRef = useRef();
const {update} = useFloating();
const arrowCallback = useCallback(
  (node) => {
    arrowRef.current = node;
    update();
  },
  [update]
);
 
<div ref={arrowCallback} />;

Virtual Element

See Virtual Elements for details.

import {useLayoutEffect} from 'react';
import {useFloating, shift} from '@floating-ui/react-dom';
 
function App() {
  const {x, y, reference, floating, strategy} = useFloating();
 
  useLayoutEffect(() => {
    // Call reference with the virtual element inside an effect
    // or event handler.
    reference({
      getBoundingClientRect() {
        return {
          // ...
        };
      },
    });
  }, [reference]);
 
  return (
    <div
      ref={floating}
      style={{
        position: strategy,
        top: y ?? 0,
        left: x ?? 0,
      }}
    >
      Tooltip
    </div>
  );
}

Variables inside middleware functions

To support React Fast Refresh, the hook uses a deepEqual utility function to ensure the position is updated whenever middleware change. Function options are compared as strings.

As a result, variables are not "fresh" inside functions passed to middleware as an option. Instead, use a ref to access data inside them.

const [n, setN] = useState(10);
const nRef = useRef(10);
 
useFloating({
  middleware: [
    offset(() => {
      // This won't work
      return n;
      // This will work
      return nRef.current;
    }),
  ],
});

Passing it inside the current component scope works as expected:

useFloating({
  middleware: [offset(n)],
});

Testing

The position of your floating element is computed asynchronously, so a state update occurs during a Promise microtask.

The state update happens after your test completes, resulting in an act warning like this:

Warning: An update to App inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
  /* fire events that update state */
});
/* assert on the output */

This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act

There are a couple of ways to fix this:

  1. Explicitly call cleanup() at the end of your test if you don't care about the positioning:
import {render, cleanup, screen} from '@testing-library/react';
 
test('example', () => {
  render(<Tooltip content="hello" open />);
  expect(screen.getByRole('tooltip').textContent).toBe('hello');
 
  // The positioning does not matter
  cleanup();
});
  1. Flush the microtask queue before the test finishes. This also allows you to ensure the floating element has been successfully positioned.
test('example', async () => {
  render(<Tooltip open />);
  await act(async () => {});
  // Position is ready, and `act` warning is gone!
});

Effects

As the position is calculated asynchronously, if you want to perform focus, ensure you wait for the position to be ready in order to prevent unwanted scrolling. This can be achieved with useLayoutEffect and requestAnimationFrame.

useLayoutEffect(() => {
  if (open) {
    // Runs after microtasks (position calculation) but before paint.
    requestAnimationFrame(() => {
      // Position is now ready!
      someElement.focus();
      someElement.scrollIntoView();
    });
  }
}, [open]);

Types

The useFloating() hook is generic to allow specifying a narrower reference type. By default, the reference type is wide, allowing Element | VirtualElement | null.

Due to the VirtualElement type, you couldn't check if the reference element contains another node for example.

To fix this, you can pass a generic argument to the hook the specify a more specific type for the reference:

import {useFloating, shift} from '@floating-ui/react-dom';
 
function App() {
  const {reference, floating, refs} =
    useFloating<HTMLButtonElement>({
      placement: 'right',
      middleware: [shift()],
    });
 
  useEffect(() => {
    if (refs.reference.current?.contains(anotherElement)) {
      // do something
    }
  }, [refs.reference]);
 
  return (
    <>
      <button ref={reference}>Button</button>
      <div ref={floating}>Tooltip</div>
    </>
  );
}