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