Skip to content

React

This package provides React bindings for @floating-ui/dom, a library that provides anchor positioning, and also interaction primitives to build floating UI components.

With this package, you can create components such as tooltips, popovers, dropdown menus, hover cards, modal dialogs, select menus, comboboxes, and more.

Goals

  • Provide building blocks: pre-built floating components aren’t exported, rather the primitives necessary to create different types of them.
  • Flexible and low-level: at the cost of more code to setup, you get high control and flexibility.
  • Accessibility is prioritized: first-class support is provided for creating accessible floating elements that work with assistive technology (keyboard usage and screen readers).

Install

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

Usage

There are two main parts to creating floating elements:

Positioning

useFloating()useFloating() is the main hook of each package.

import {useFloating} from '@floating-ui/react';
 
function App() {
  const {refs, floatingStyles} = useFloating();
 
  return (
    <>
      <button ref={refs.setReference}>Button</button>
      <div ref={refs.setFloating} style={floatingStyles}>
        Tooltip
      </div>
    </>
  );
}
import {useFloating} from '@floating-ui/react';
 
function App() {
  const {refs, floatingStyles} = useFloating();
 
  return (
    <>
      <button ref={refs.setReference}>Button</button>
      <div ref={refs.setFloating} style={floatingStyles}>
        Tooltip
      </div>
    </>
  );
}

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

  • refs.setReferencerefs.setReference is the reference (or anchor) element that is being referred to for positioning.
  • refs.setFloatingrefs.setFloating is the floating element that is being positioned relative to the reference element.
  • floatingStylesfloatingStyles is an object of positioning styles to apply to the floating element’s stylestyle prop.

The refs are functions to make them reactive — this ensures changes to the reference or floating elements, such as with conditional rendering, are handled correctly by updating the position.

External elements

Depending on your component tree, you may instead want to synchronize with an external element instead of using refs.

function App() {
  const [anchor, setAnchor] = useState(null);
  return (
    <>
      <button ref={setAnchor}>Button</button>
      <Tooltip anchor={anchor} />
    </>
  );
}
 
function Tooltip({anchor}) {
  const {refs, floatingStyles} = useFloating({
    elements: {
      reference: anchor,
    },
  });
 
  return (
    <div ref={refs.setFloating} style={floatingStyles}>
      Tooltip
    </div>
  );
}
function App() {
  const [anchor, setAnchor] = useState(null);
  return (
    <>
      <button ref={setAnchor}>Button</button>
      <Tooltip anchor={anchor} />
    </>
  );
}
 
function Tooltip({anchor}) {
  const {refs, floatingStyles} = useFloating({
    elements: {
      reference: anchor,
    },
  });
 
  return (
    <div ref={refs.setFloating} style={floatingStyles}>
      Tooltip
    </div>
  );
}

Either element can be synchronized externally or via refs.set{Element}refs.set{Element}.

Positioning styles customization

By default, the floating element is positioned using transformtransform in the floatingStyles object. This is the most performant way to position elements, but can be disabled:

useFloating({transform: false});
useFloating({transform: false});

If you’d like to retain transform styles while allowing transform animations, create a wrapper, where the outermost node is the positioned one, and the inner is the actual styled element.

The hook also returns the coordinates and positioning strategy directly if floatingStyles is not suitable:

const {x, y, strategy} = useFloating();
return (
  <div
    style={{
      position: strategy,
      left: x,
      top: y,
      width: 'max-content',
    }}
  />
);
const {x, y, strategy} = useFloating();
return (
  <div
    style={{
      position: strategy,
      left: x,
      top: y,
      width: 'max-content',
    }}
  />
);

Anchoring

To ensure the floating element remains anchored to its reference element in a variety of scenarios without detaching, you can pass the autoUpdate utility to the whileElementsMountedwhileElementsMounted prop:

import {useFloating, autoUpdate} from '@floating-ui/react';
 
// Inside your component
useFloating({
  whileElementsMounted: autoUpdate,
});
import {useFloating, autoUpdate} from '@floating-ui/react';
 
// Inside your component
useFloating({
  whileElementsMounted: autoUpdate,
});

This prop is called whenever both the reference and floating elements are mounted, and cleans up if they change. This allows listeners that automatically update the position to be registered.

To pass options to autoUpdate:

useFloating({
  whileElementsMounted(...args) {
    const cleanup = autoUpdate(...args, {animationFrame: true});
    // Important! Always return the cleanup function.
    return cleanup;
  },
});
useFloating({
  whileElementsMounted(...args) {
    const cleanup = autoUpdate(...args, {animationFrame: true});
    // Important! Always return the cleanup function.
    return cleanup;
  },
});

Manual updating

The hook returns an update()update() function to update the position at will, manually:

const {update} = useFloating();
<Panel onResize={update} />;
const {update} = useFloating();
<Panel onResize={update} />;

Return value

The hook returns all the values from computePosition, plus some extras to work with React. This includes data about the final placement and middleware data which are useful when rendering.

Options

The hook accepts all the options from computePosition, which allows you to customize the position. Here’s an example:

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

Middleware can alter the positioning from the basic placementplacement, act as visibility optimizers, or provide data to use.

The docs for the middleware that were passed are available here:

All of these are re-exported from the base @floating-ui/dom library.

Refs

To access the DOM elements, you can either access the refs:

const {refs} = useFloating();
 
// Inside an effect or event handler:
refs.reference.current;
refs.floating.current;
const {refs} = useFloating();
 
// Inside an effect or event handler:
refs.reference.current;
refs.floating.current;

Or the elements directly:

const {elements} = useFloating();
 
// During render, unlike the refs:
elements.reference;
elements.floating;
const {elements} = useFloating();
 
// During render, unlike the refs:
elements.reference;
elements.floating;

External elements can be synchronized using the elementselements option like so, if they live outside the component:

function MyComponent({referenceEl, floatingEl}) {
  const {refs} = useFloating({
    elements: {
      reference: referenceEl,
      floating: floatingEl,
    },
  });
}
function MyComponent({referenceEl, floatingEl}) {
  const {refs} = useFloating({
    elements: {
      reference: referenceEl,
      floating: floatingEl,
    },
  });
}

Effects

Positioning is done in an async function, which means the position is ready during a microtask, after layout effects are executed. This means initially, the floating element is situated at the top-left (0, 0) of its offset container — so calling DOM methods that cause side-effects like scrolling will result in unexpected behavior.

The hook returns an isPositionedisPositioned boolean that lets you know if the floating element has been positioned:

const [isOpen, setIsOpen] = useState(false);
const {isPositioned} = useFloating({
  // Synchronize `isPositioned` with an `open` state.
  open: isOpen,
});
 
// Each time the floating element opens, we want to focus and
// scroll some element into view.
useLayoutEffect(() => {
  if (isPositioned) {
    someElement.focus();
    someElement.scrollIntoView();
  }
}, [isPositioned]);
const [isOpen, setIsOpen] = useState(false);
const {isPositioned} = useFloating({
  // Synchronize `isPositioned` with an `open` state.
  open: isOpen,
});
 
// Each time the floating element opens, we want to focus and
// scroll some element into view.
useLayoutEffect(() => {
  if (isPositioned) {
    someElement.focus();
    someElement.scrollIntoView();
  }
}, [isPositioned]);

The openopen option accepts a boolean that represents the open/close state of the floating element. This ensures you can wait each time it opens when the host component does not unmount, which is necessary in cases where the reference element relocates on the page.

Arrow

The arrow module exported from this package allows refs in addition to elements:

import {arrow} from '@floating-ui/react';
 
// Inside your component
const arrowRef = useRef(null);
useFloating({
  middleware: [
    arrow({
      element: arrowRef,
    }),
  ],
});
import {arrow} from '@floating-ui/react';
 
// Inside your component
const arrowRef = useRef(null);
useFloating({
  middleware: [
    arrow({
      element: arrowRef,
    }),
  ],
});

If you need your arrow to be reactive to updates (e.g. showing or hiding the arrow with conditional rendering while the floating element is open), you should use state instead. Alternatively, you can use visibility: hiddenvisibility: hidden CSS to hide it and keep using a plain ref.

For details on creating an arrow element, see the arrow middleware page.

Testing

When testing your components, ensure you flush microtasks immediately after the floating element renders. This will avoid the act warning.

import {act} from '@testing-library/react';
 
test('something', async () => {
  render(<Tooltip open />);
  await act(async () => {}); // Flush microtasks.
  // Position state is ready by this line.
});
import {act} from '@testing-library/react';
 
test('something', async () => {
  render(<Tooltip open />);
  await act(async () => {}); // Flush microtasks.
  // Position state is ready by this line.
});

You may use this a lot, so you can create a custom function:

const waitForPosition = () => act(async () => {});
 
test('something', async () => {
  render(<Tooltip open />);
  await waitForPosition();
  expect(screen.queryByRole('tooltip')).toBeInTheDocument();
});
const waitForPosition = () => act(async () => {});
 
test('something', async () => {
  render(<Tooltip open />);
  await waitForPosition();
  expect(screen.queryByRole('tooltip')).toBeInTheDocument();
});

Narrow reference type

Because the refs.setReferencerefs.setReference callback ref accepts a virtual element, you may need to narrow the type when performing DOM operations on the ref:

const {refs} = useFloating<HTMLButtonElement>();
 
// @floating-ui/react
// refs.domReference.current is now of type HTMLButtonElement
 
// @floating-ui/react-dom
// refs.reference.current is now of type HTMLButtonElement
const {refs} = useFloating<HTMLButtonElement>();
 
// @floating-ui/react
// refs.domReference.current is now of type HTMLButtonElement
 
// @floating-ui/react-dom
// refs.reference.current is now of type HTMLButtonElement

In the full package, it is narrowed on refs.domReferencerefs.domReference, as the position can be separated.

Option reactivity

When using React state and middleware, stateful values inside function options (useful when deriving from middleware state) aren’t fresh or reactive.

const [value, setValue] = useState(0);
// Reactive and fresh:
offset(value);
// Not reactive or fresh:
offset(() => value);
const [value, setValue] = useState(0);
// Reactive and fresh:
offset(value);
// Not reactive or fresh:
offset(() => value);

To ensure they are reactive, specify the dependencies in the optionsoptions key:

const [value, setValue] = useState(0);
 
useFloating({
  middleware: [
    {
      ...offset(() => value),
      options: [value],
    },
  ],
});
const [value, setValue] = useState(0);
 
useFloating({
  middleware: [
    {
      ...offset(() => value),
      options: [value],
    },
  ],
});

This is where the compatibility with the leaner @floating-ui/react-dom package ends. The following docs now only apply to @floating-ui/react.

View examples, or read below to understand the basics.

Interactions

Designed for all inputsSafe cursor polygon

To add interactions, such as the ability to only show a floating element while hovering over its reference element, the hook must first accept the following two options:

  • openopen — a boolean that represents whether the floating element is currently rendered.
  • onOpenChangeonOpenChange — an event callback invoked when the open boolean state should change.
import {useFloating} from '@floating-ui/react';
 
function App() {
  const [isOpen, setIsOpen] = useState(false);
 
  const {refs, floatingStyles} = useFloating({
    open: isOpen,
    onOpenChange: setIsOpen,
  });
 
  return (
    <>
      <button ref={refs.setReference}>Button</button>
      {isOpen && (
        <div ref={refs.setFloating} style={floatingStyles}>
          Tooltip
        </div>
      )}
    </>
  );
}
import {useFloating} from '@floating-ui/react';
 
function App() {
  const [isOpen, setIsOpen] = useState(false);
 
  const {refs, floatingStyles} = useFloating({
    open: isOpen,
    onOpenChange: setIsOpen,
  });
 
  return (
    <>
      <button ref={refs.setReference}>Button</button>
      {isOpen && (
        <div ref={refs.setFloating} style={floatingStyles}>
          Tooltip
        </div>
      )}
    </>
  );
}

Note that floating components do not always require “anchor positioning”, so floatingStyles can be ignored.

Interaction hooks

Interaction hooks allow the open state to change, among other functionality. Each interaction hook accepts the contextcontext object which gets returned from useFloating()useFloating() as their first argument:

import {
  useFloating,
  useInteractions,
  useHover,
  useFocus,
} from '@floating-ui/react';
 
// Inside your component
const {refs, context} = useFloating({
  open: isOpen,
  onOpenChange: setIsOpen,
});
 
const hover = useHover(context);
const focus = useFocus(context);
 
const {getReferenceProps, getFloatingProps} = useInteractions([
  hover,
  focus,
]);
import {
  useFloating,
  useInteractions,
  useHover,
  useFocus,
} from '@floating-ui/react';
 
// Inside your component
const {refs, context} = useFloating({
  open: isOpen,
  onOpenChange: setIsOpen,
});
 
const hover = useHover(context);
const focus = useFocus(context);
 
const {getReferenceProps, getFloatingProps} = useInteractions([
  hover,
  focus,
]);

The useHover()useHover() and useFocus()useFocus() hooks set up effects and return event handler props to change the open state, the latter of which get merged by useInteractions()useInteractions() for rendering.

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

Prop getters

The prop getters are used to add event handlers returned from the interaction hooks, among other functionality, to the reference and floating elements. When called, they return an object of props like onFocusonFocus.

<>
  <button ref={refs.setReference} {...getReferenceProps()}>
    My button
  </button>
  <div
    ref={refs.setFloating}
    style={floatingStyles}
    {...getFloatingProps()}
  >
    My tooltip
  </div>
</>
<>
  <button ref={refs.setReference} {...getReferenceProps()}>
    My button
  </button>
  <div
    ref={refs.setFloating}
    style={floatingStyles}
    {...getFloatingProps()}
  >
    My tooltip
  </div>
</>

All custom event listener props, such as onClickonClick, onKeyDownonKeyDown 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 overwriting them.

// ❌ Your `onClick` can be overwritten:
<div
  onClick={() => {
    // Potentially overwritten by the props below.
  }}
  {...getReferenceProps()}
/>
// ❌ Your `onClick` can be overwritten:
<div
  onClick={() => {
    // Potentially overwritten by the props below.
  }}
  {...getReferenceProps()}
/>
// ✅ Merging works inside `getReferenceProps()`:
<div
  {...getReferenceProps({
    onClick() {
      // Will not be overwritten.
    },
  })}
/>
// ✅ Merging works inside `getReferenceProps()`:
<div
  {...getReferenceProps({
    onClick() {
      // Will not be overwritten.
    },
  })}
/>

You may find passing all props through the prop getter helps you remember to prevent overriding event handlers, but is not currently required unless the value is a function event handler that starts with on.

getItemProps

const {getItemProps} = useInteractions([]);
const {getItemProps} = useInteractions([]);

This is an optional prop getter that is only used when dealing with a list inside your floating element (see useListNavigation).

Open event

The onOpenChangeonOpenChange event callback is invoked with an (optional) event as a second parameter:

useFloating({
  onOpenChange(isOpen, event) {
    setIsOpen(isOpen);
    if (event) {
      // The event can be determined. This callback may be invoked
      // in an effect, so the event is not available.
    }
  },
});
useFloating({
  onOpenChange(isOpen, event) {
    setIsOpen(isOpen);
    if (event) {
      // The event can be determined. This callback may be invoked
      // in an effect, so the event is not available.
    }
  },
});

Note that onOpenChangeonOpenChange is not called if you manually changed the open state via the setIsOpen()setIsOpen() setter. You can derive the event yourself in this case anyway.

Data

The context object contains a data ref with some information available.

const {context} = useFloating();
 
useEffect(() => {
  console.log(context.dataRef.current);
}, [context]);
const {context} = useFloating();
 
useEffect(() => {
  console.log(context.dataRef.current);
}, [context]);

Currently, only one built-in value is set:

interface ContextData {
  // Which event caused the floating element to open.
  openEvent?: Event;
 
  // Add support for custom data to pass around.
  [key: string]: any;
}
interface ContextData {
  // Which event caused the floating element to open.
  openEvent?: Event;
 
  // Add support for custom data to pass around.
  [key: string]: any;
}

Changing the positioning reference while retaining events

  • refs.setReferencerefs.setReference element is both the events and position reference by default.
  • refs.setPositionReferencerefs.setPositionReference allows you to separate the position to another element (either real or virtual).
const {refs} = useFloating();
 
return (
  <>
    <button ref={refs.setReference} {...getReferenceProps()}>
      Event reference
    </button>
    <button ref={refs.setPositionReference}>
      Position reference
    </button>
  </>
);
const {refs} = useFloating();
 
return (
  <>
    <button ref={refs.setReference} {...getReferenceProps()}>
      Event reference
    </button>
    <button ref={refs.setPositionReference}>
      Position reference
    </button>
  </>
);

View on CodeSandbox

Multiple floating elements on a single reference element

import {useMergeRefs} from '@floating-ui/react';
import {useMergeRefs} from '@floating-ui/react';

Refs can be merged with the useMergeRefs hook, and props can be merged by calling one of the getters inside of the other:

const {refs: tooltipRefs} = useFloating();
const {refs: menuRefs} = useFloating();
 
const {getReferenceProps: getTooltipReferenceProps} =
  useInteractions([]);
const {getReferenceProps: getMenuReferenceProps} =
  useInteractions([]);
 
const ref = useMergeRefs([
  tooltipRefs.setReference,
  menuRefs.setReference,
]);
const props = getTooltipReferenceProps(getMenuReferenceProps());
 
return (
  <button ref={ref} {...props}>
    Common reference
  </button>
);
const {refs: tooltipRefs} = useFloating();
const {refs: menuRefs} = useFloating();
 
const {getReferenceProps: getTooltipReferenceProps} =
  useInteractions([]);
const {getReferenceProps: getMenuReferenceProps} =
  useInteractions([]);
 
const ref = useMergeRefs([
  tooltipRefs.setReference,
  menuRefs.setReference,
]);
const props = getTooltipReferenceProps(getMenuReferenceProps());
 
return (
  <button ref={ref} {...props}>
    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 disableddisabled 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