Tooltips

A tooltip is a floating element that displays information related to a button or 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.
  • COMING SOON: 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.

Example

This is a functional Tooltip that uses a combination of hooks and components, each of which is described in the sections below.

Open State

ts
let open = $state(false);

open 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:

ts
const floating = useFloating({ /* ...settings... */ });
  • 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 open 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

The useInteractions() hooks returns an object 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.

ts
const hover = useHover(floating.context);
const role = useRole(floating.context, { role: 'tooltip' });
const interactions = useInteractions([hover, 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.
  • COMING SOON: useFocus(): adds the ability to toggle the tooltip open or closed when the reference element is focused.
  • COMING SOON: 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.

Rendering

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

html
<!-- Reference Element -->
<button bind:this={elemReference} {...interactions.getReferenceProps()}>Hover Me</button>

<!-- Floating Element -->
<div
	bind:this={elemFloating}
	style={floating.floatingStyles}
	{...interactions.getFloatingProps()}
	class="floating"
>
	{#if open}
		<div>
			<p>This is the floating element</p>
			<FloatingArrow bind:ref={elemArrow} context={floating.context} />
		</div>
	{/if}
</div>

{...getReferenceProps()} and {...getFloatingProps()} spreads the props from the interaction Hooks onto the relevant elements. They contain props like onMouseEnter, aria-describedby, etc.