Skip to content
Tooltip

A tooltip is a floating element that displays information related to an anchor element when it receives keyboard focus or the mouse hovers over it.

Essentials

An accessible tooltip component has the following qualities:

  • Dynamic anchor positioning: The tooltip is positioned next to its reference element, and remains anchored to it while avoiding collisions.
  • Events: When the mouse hovers over the reference element, or when the reference element receives keyboard focus, the tooltip opens. When the mouse leaves, or the reference is blurred, the tooltip closes.
  • Dismissal: When the user presses the esc key while the tooltip is open, it closes.
  • Role: The elements are given relevant role and ARIA attributes to be accessible to screen readers.

Examples

Both of these examples have sections explaining them in-depth below.

Basic tooltip

CodeSandbox demo

This example demonstrates how to create a tooltip for use in a single instance to familiarize yourself with the fundamentals.

Let’s walk through the example:

Open state

import {useState} from 'react';
 
function Tooltip() {
  const [isOpen, setIsOpen] = useState(false);
}

isOpen determines whether or not the tooltip is currently open on the screen. It is used for conditional rendering.

useFloating Hook

The useFloating() Hook provides positioning and context for our tooltip. We need to pass it some information:

  • open: The open state from our useState() Hook above.
  • onOpenChange: A callback function that will be called when the tooltip is opened or closed. We’ll use this to update our isOpen state.
  • middleware: Import and pass middleware to the array that ensure the tooltip remains on the screen, no matter where it ends up being positioned.
  • whileElementsMounted: Ensure the tooltip remains anchored to the reference element by updating the position when necessary, only while both the reference and floating elements are mounted for performance.
import {
  useFloating,
  autoUpdate,
  offset,
  flip,
  shift,
} from '@floating-ui/react';
 
function Tooltip() {
  const [isOpen, setIsOpen] = useState(false);
 
  const {refs, floatingStyles, context} = useFloating({
    open: isOpen,
    onOpenChange: setIsOpen,
    middleware: [offset(10), flip(), shift()],
    whileElementsMounted: autoUpdate,
  });
}

Interaction Hooks

Interaction Hooks return objects containing keys of props that enable the tooltip to be opened, closed, or accessible to screen readers.

Using the context that was returned from the Hook, call the interaction Hooks:

import {
  // ...
  useHover,
  useFocus,
  useDismiss,
  useRole,
  useInteractions,
} from '@floating-ui/react';
 
function Tooltip() {
  const [isOpen, setIsOpen] = useState(false);
 
  const {refs, floatingStyles, context} = useFloating({
    open: isOpen,
    onOpenChange: setIsOpen,
    middleware: [offset(10), flip(), shift()],
    whileElementsMounted: autoUpdate,
  });
 
  const hover = useHover(context, {move: false});
  const focus = useFocus(context);
  const dismiss = useDismiss(context);
  const role = useRole(context, {
    // If your reference element has its own label (text).
    role: 'tooltip',
    // If your reference element does not have its own label,
    // e.g. an icon.
    role: 'label',
  });
 
  // Merge all the interactions into prop getters
  const {getReferenceProps, getFloatingProps} = useInteractions([
    hover,
    focus,
    dismiss,
    role,
  ]);
}
  • useHover() adds the ability to toggle the tooltip open or closed when the reference element is hovered over. The move option is set to false so that mousemove events are ignored.
  • useFocus() adds the ability to toggle the tooltip open or closed when the reference element is focused.
  • useDismiss() adds the ability to dismiss the tooltip when the user presses the esc key.
  • useRole() adds the correct ARIA attributes for a tooltip to the tooltip and reference elements.

Finally, useInteractions() merges all of their props into prop getters which can be used for rendering.

Rendering

Now we have all the variables and Hooks set up, we can render out our elements.

function Tooltip() {
  // ...
  return (
    <>
      <button ref={refs.setReference} {...getReferenceProps()}>
        Reference element
      </button>
      {isOpen && (
        <div
          ref={refs.setFloating}
          style={floatingStyles}
          {...getFloatingProps()}
        >
          Tooltip element
        </div>
      )}
    </>
  );
}
  • {...getReferenceProps()} / {...getFloatingProps()} spreads the props from the interaction Hooks onto the relevant elements. They contain props like onMouseEnter, aria-describedby, etc.

Reusable tooltip component

CodeSandbox demo

It is better to create a reusable component API that can be used in a variety of different scenarios more easily. We can place all of our Hooks into a single custom Hook for better reusability, which is then used by a controller component which encapsulates the state.

The reusable component can:

  • Be uncontrolled or controlled
  • Accept any element as the <TooltipTrigger />
  • Read the open state to change styles
function App() {
  return (
    <Tooltip>
      <TooltipTrigger>My trigger</TooltipTrigger>
      <TooltipContent>My tooltip</TooltipContent>
    </Tooltip>
  );
}

Controller component

  • <Tooltip />

This is the controller component that manages the tooltip’s state and provides the API to the rest of the components.

Render components

These components read the context provided by the root Tooltip component and render the appropriate elements.

The components must be wrapped in forwardRef() to allow refs, and should merge the refs to ensure all refs are preserved and forwarded to the element. Props are also merged to prevent overwriting.

  • <TooltipTrigger /> is the trigger button the tooltip is attached to. This accepts an asChild prop if you want to attach it to a custom element. It also has a data-state attached to style based on the open/closed state.
  • <TooltipContent /> is the tooltip element, which can contain any children (React nodes).

Delay groups

One of the most useful UX improvements for tooltips is making nearby tooltips share a delay.

<FloatingDelayGroup delay={200}>
  <Tooltip>
    <TooltipTrigger>Ref 1</TooltipTrigger>
    <TooltipContent className="Tooltip">Label 1</TooltipContent>
  </Tooltip>
  <Tooltip>
    <TooltipTrigger>Ref 2</TooltipTrigger>
    <TooltipContent className="Tooltip">Label 2</TooltipContent>
  </Tooltip>
  <Tooltip>
    <TooltipTrigger>Ref 3</TooltipTrigger>
    <TooltipContent className="Tooltip">Label 3</TooltipContent>
  </Tooltip>
</FloatingDelayGroup>

Disabled buttons

Sometimes you want to disable a button, but still show the tooltip while it’s disabled.

Disabling a button with a tooltip prevents it from being accessible, but can be worked around using a different prop. This supplants the disabled prop to allow events to fire, including keyboard access.

const Button = React.forwardRef(function Button(
  {visuallyDisabled, disabled, ...props},
  ref,
) {
  return (
    <button
      {...props}
      ref={ref}
      disabled={visuallyDisabled ? undefined : disabled}
      aria-disabled={visuallyDisabled ? 'true' : undefined}
      // You'll want to do this for all relevant event handlers.
      onClick={visuallyDisabled ? undefined : props.onClick}
    />
  );
});