Skip to content

React DOM Interactions

This package provides interaction primitives in the form of hooks and components that compose together to build higher-level floating UI components.

Designed for all inputsSafe cursor polygon

View examples below.

While the positioning package provides the "where" of a floating element, this package provides the "when" and "how". When does the floating element open on the screen, and how does the user interact with it?

This makes it possible to easily build a <Tooltip />, <Popover />, <Select />, <Dropdown /> and other floating components.

  • Full freedom: Mix and match your own custom logic while using the primitives as accessibility helpers to build the interactions you need.
  • Your own components and hooks: This package is not intended to be a component library that provides out-of-the-box behavior — instead, you create your own components to wrap the hooks and primitive components.
  • Advanced behavior: Infinitely nestable floating tree structures, delay groups, & more.

All of this is fully treeshakeable, so import what you need and the rest will be eliminated by your bundler.

Install

npm install @floating-ui/react-dom-interactions

This package is a superset of @floating-ui/react-dom, so you only need to install the above package.

Usage

The useFloating() hook exported from this package accepts an open boolean and onOpenChange as an option to change that value (plus all the positioning props as listed in the React DOM docs).

You use this boolean to conditionally render the floating element.

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

Note that floating components do not always require "anchored positioning" — for instance a modal dialog centered in the viewport. So the x and y positioning props are optional.

Hooks

useInteractions() accepts an array of called hooks in the following predictable form:

import {
  useFloating,
  useInteractions,
  useHover,
  useFocus,
  useRole,
} from '@floating-ui/react-dom-interactions';
 
// ...
const {context} = useFloating();
const {getReferenceProps, getFloatingProps, getItemProps} =
  useInteractions([
    useHover(context, props),
    useFocus(context, props),
    useRole(context, props),
  ]);

Each hook accepts the context object which gets returned from useFloating() as its first argument. Props are passed as a second argument.

This API enables each of the hooks to be fully tree-shakeable. The navigation bar on the left explains them in detail.

Avoid importing all the hooks together like import * as Interactions from '@floating-ui/react-dom-interactions' as this will nullify the benefits of tree-shaking.

Prop getters

useInteractions() returns prop getters — functions you call to return props that spread on the elements:

<>
  <button ref={reference} {...getReferenceProps()}>
    My button
  </button>
  <div
    ref={floating}
    style={{
      position: strategy,
      left: x ?? 0,
      top: y ?? 0,
    }}
    {...getFloatingProps()}
  >
    My tooltip
  </div>
</>

All custom event listener props, such as onClick, onKeyDown and more you pass to the element should be specified inside the prop getter.

They perform merging of their own internal event listeners and your own without overriding them.

// ❌ Your `onClick` can be overridden
<div
  onClick={() => {
    console.log('Overridden by props below');
  }}
  {...getReferenceProps()}
/>
// ✅ Merging works inside `getReferenceProps()`
<div
  {...getReferenceProps({
    onClick() {
      console.log(
        'This will not override, or be overridden by ',
        'the interaction hooks.'
      );
    },
  })}
/>

You may find passing all props through the prop getter helps you remember to prevent overriding event handlers, but is not currently required for non-event handler function props.

Item prop getter

When dealing with a list inside your floating element (useListNavigation()), pass these props to each item element:

<div
  ref={floating}
  style={{
    position: strategy,
    left: x ?? 0,
    top: y ?? 0,
  }}
  {...getFloatingProps()}
>
  <ul>
    {list.map((item) => (
      <li key={item} {...getItemProps()}>
        {item}
      </li>
    ))}
  </ul>
</div>

Without a list of items, this prop getter can be omitted (e.g. a regular tooltip or popover).

Changing the positioning reference while retaining events

If you want anchored positioning to be relative to a different reference element than the one that receives events, this is possible.

You may use a virtual element that copies over the positioning reference's data by calling the reference callback ref with it. Make sure you continue to pass the callback ref to the element that should receive events as its ref prop.

const positioningRef = useRef();
const {reference} = useFloating();
 
useLayoutEffect(() => {
  reference({
    getBoundingClientRect: () =>
      positioningRef.current.getBoundingClientRect(),
    contextElement: positioningRef.current,
  });
}, [reference]);
 
return (
  <div ref={positioningRef}>
    <button ref={reference} {...getReferenceProps()}>
      Event reference
    </button>
  </div>
);

View on CodeSandbox

Multiple floating elements on a single reference element

Merge the reference refs on the element for each useFloating(), and call the getReferenceProps() prop getters inside one another.

const {reference: tooltipReference} = useFloating();
const {reference: menuReference} = useFloating();
 
const {getReferenceProps: getTooltipReferenceProps} =
  useInteractions([]);
const {getReferenceProps: getMenuReferenceProps} =
  useInteractions([]);
 
return (
  <>
    <button
      // `react-merge-refs` or similar library works fine.
      ref={mergeRefs([tooltipReference, menuReference])}
      // You can call one of the prop getters inside another one.
      // They will merge as expected.
      {...getTooltipReferenceProps(getMenuReferenceProps())}
    >
      Common reference
    </button>
  </>
);

View on CodeSandbox

Disabled elements

Disabled elements don't fire events, so tooltips attached to disabled buttons don't show. Avoid using the disabled prop, and make the button visually disabled instead. This ensures you won't need any wrapper tags and makes the tooltip accessible to all users.

View on CodeSandbox

Examples

Here are a few examples that show how to compose the interaction hooks and components together to build a higher-level component.

These are just examples. The component APIs are not designed perfectly, but you can use them as a starting point for your own components and design the API however you like.


One of the ideas behind the primitive hooks is to give you this freedom without prescribing a restricting component API design upon you.

Tooltip

Popover

Dialog

Context Menu

Select (Listbox)