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
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
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 ouruseState()
Hook above.onOpenChange
: A callback function that will be called when the tooltip is opened or closed. We’ll use this to update ourisOpen
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.
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:
useHover()
adds the ability to toggle the tooltip open or closed when the reference element is hovered over. Themove
option is set to false so thatmousemove
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 theesc
key.useRole()
adds the correct ARIA attributes for atooltip
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.
{...getReferenceProps()}
/{...getFloatingProps()}
spreads the props from the interaction Hooks onto the relevant elements. They contain props likeonMouseEnter
,aria-describedby
, etc.
Reusable tooltip component
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
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 anasChild
prop if you want to attach it to a custom element. It also has adata-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.
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.