Skip to content

React DOM

npm install @floating-ui/react-dom

This page details the positioning hook. To build interactions for floating elements, check out React DOM Interactions.

Usage

The useFloating() hook accepts all of computePosition's options.

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

x and y will be null initially, before the layout effect has fired.

useFloating() only calculates the position once on render, or when the reference/floating elements changed. Depending on the context in which the floating element lives, you'll likely need to update its position in an effect. See Updating.

Stable ref prop

Ensure the ref prop values you pass to the reference and floating elements remain stable across renders to prevent infinite loops.

If you want to merge another ref with the one returned from useFloating(), memo the value:

const stableRef = useMemo(
  () => mergeRefs([userRef, reference]),
  [userRef, reference]
);
 
return <div ref={stableRef} />;

External reference

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

function MyComponent({triggerRef}) {
  const {reference} = useFloating();
 
  useLayoutEffect(() => {
    reference(triggerRef.current);
  }, [triggerRef.current]);
 
  // You may need to disable the React Hooks ESLint rule for the
  // dependency array.
}

Internal refs

const {refs} = useFloating();

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
});

Updating

The hook returns an update() function to update the position, e.g. in event listeners.

const {update} = useFloating();

The autoUpdate utility is the recommended way to update the floating element when required, but you can use whatever method you please.

import {useEffect} from 'react';
import {
  useFloating,
  shift,
  autoUpdate,
} from '@floating-ui/react-dom';
 
function App() {
  const {x, y, reference, floating, strategy, update, refs} =
    useFloating({
      placement: 'right',
      middleware: [shift()],
    });
 
  useEffect(() => {
    if (!refs.reference.current || !refs.floating.current) {
      return;
    }
 
    // Only call this when the floating element is rendered
    return autoUpdate(
      refs.reference.current,
      refs.floating.current,
      update
    );
  }, [refs.reference, refs.floating, update]);
 
  return (
    <>
      <button ref={reference}>Button</button>
      <div
        ref={floating}
        style={{
          position: strategy,
          top: y ?? '',
          left: x ?? '',
        }}
      >
        Tooltip
      </div>
    </>
  );
}

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>
    </>
  );
}

Conditional rendering

If you're conditionally rendering the arrow element (not just the floating element), you'll want to utilize the same technique as the reference and floating elements which is a callback function that calls update() after assigning the ref value.

<div
  ref={(node) => {
    arrowRef.current = node;
    update();
  }}
/>

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({
    placement: 'right',
    middleware: [shift()],
  });
 
  useLayoutEffect(() => {
    // Call reference with the virtual element inside an effect
    reference({
      getBoundingClientRect() {
        return {
          // ...
        };
      },
    });
  }, [reference]);
 
  return (
    <div
      ref={floating}
      style={{
        position: strategy,
        top: y ?? '',
        left: x ?? '',
      }}
    >
      Tooltip
    </div>
  );
}

Variables inside middleware functions

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

Floating UI computes the position of your floating element 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

To fix this, 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();
});

While @testing-library/react already does this internally, it's executed in the microtask queue following Floating UI's update call — so the act warning occurs.

Note: afterEach(cleanup) won't work — this is what Testing Library already does. You need to call cleanup() explicitly at the end of each of your tests.

If the position does matter in your assertions, ensure you wait for the microtask queue to be flushed using waitFor or other async utilities.

render(<Tooltip open />);
await waitFor(() => {
  // The positioning matters, so we make expect(...)
  // assertions in here.
});

Or, if needed, you can explicitly flush the queue and let the positioning update be executed:

render(<Tooltip open />);
await act(async () => {});

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>
    </>
  );
}