Skip to content

Tooltip

When the user's mouse or focus rests on an element, a non-interactive popup is displayed near it.

A couple notes on using tooltips:

  • Do not use tooltips for information vital to task completion. The elements they are attached to should usually make sense on their own, but a tooltip can help provide a little bit more information--especially for new users of your app.
  • Keep the content minimal, they are not intended to hide critical content.
  • If you want interactive content, you can use a Dialog.

Touch Events: Touch events are currently not supported. There's not a lot of research or examples of these types of tooltips on mobile. We have some ideas but need to validate them first before implementing. Please adjust your interfaces on mobile to account for this.

function Example() {  return (    <div>      <Tooltip label="Notifications">        <button style={{ fontSize: 25 }}>          <span>🔔</span>        </button>      </Tooltip>      <Tooltip label="Settings">        <button style={{ fontSize: 25 }}>          <span aria-hidden>⚙️</span>        </button>      </Tooltip>
      <div style={{ float: "right" }}>        <Tooltip label="Notifications" aria-label="3 Notifications">          <button style={{ fontSize: 25 }}>            <span>🔔</span>            <span>3</span>          </button>        </Tooltip>      </div>    </div>  );}

Installation

From the command line in your project directory, run npm install @reach/tooltip or yarn add @reach/tooltip. Then import the components and styles that you need:

npm install @reach/tooltip# oryarn add @reach/tooltip
import Tooltip, { useTooltip, TooltipPopup } from "@reach/tooltip";import "@reach/tooltip/styles.css";

Usage

function Example() {  return (    <Tooltip label="Save">      <button style={{ fontSize: 25 }}>💾</button>    </Tooltip>  );}

Sometimes the button contains information that is relevant to the user, like a badge showing the count for notifications. In these cases we want the screen reader user to know both the content in the tooltip, but also the content in the badge. For screen reader users, the only content announced to them is whatever is in the tooltip. For these cases, use the aria-label prop.

function Example() {  return (    <Tooltip      // What the tooltip renders      label="Notifications"      // What the screen reader announces      aria-label="3 Notifications"    >      <button style={{ fontSize: 25 }}>🔔 3</button>    </Tooltip>  );}

All props passed to Tooltip will go to the popover.

function Example() {  return (    <Tooltip      label="Fall in love all over again"      style={{        background: "hsla(0, 0%, 0%, 0.75)",        color: "white",        border: "none",        borderRadius: "4px",        padding: "0.5em 1em",      }}    >      <button style={{ fontSize: 25 }}>❤️</button>    </Tooltip>  );}

Component API

Tooltip

Renders … you guessed it: a tooltip!

Tooltip CSS Selectors

Please see the styling guide.

[data-reach-tooltip] {}

useTooltip, TooltipPopup

Tooltip is built on top of the useTooltip hook and TooltipPopup.

You may want to drop down to this lower level API if you need animation or you want to add the triangles to the tooltip. Check out the demos below for examples.

Demos

Triangle pointers and custom styles

(() => {  // Center the tooltip, but collisions will win  const centered = (triggerRect, tooltipRect) => {    const triggerCenter = triggerRect.left + triggerRect.width / 2;    const left = triggerCenter - tooltipRect.width / 2;    const maxLeft = window.innerWidth - tooltipRect.width - 2;    return {      left: Math.min(Math.max(2, left), maxLeft) + window.scrollX,      top: triggerRect.bottom + 8 + window.scrollY,    };  };
  function TriangleTooltip({ children, label, "aria-label": ariaLabel }) {    // get the props from useTooltip    const [trigger, tooltip] = useTooltip();
    // destructure off what we need to position the triangle    const { isVisible, triggerRect } = tooltip;
    return (      <React.Fragment>        {React.cloneElement(children, trigger)}
        {isVisible && (          // The Triangle. We position it relative to the trigger, not the popup          // so that collisions don't have a triangle pointing off to nowhere.          // Using a Portal may seem a little extreme, but we can keep the          // positioning logic simpler here instead of needing to consider          // the popup's position relative to the trigger and collisions          <Portal>            <div              style={{                position: "absolute",                left:                  triggerRect && triggerRect.left - 10 + triggerRect.width / 2,                top: triggerRect && triggerRect.bottom + window.scrollY,                width: 0,                height: 0,                borderLeft: "10px solid transparent",                borderRight: "10px solid transparent",                borderBottom: "10px solid black",              }}            />          </Portal>        )}        <TooltipPopup          {...tooltip}          label={label}          aria-label={ariaLabel}          style={{            background: "black",            color: "white",            border: "none",            borderRadius: "3px",            padding: "0.5em 1em",          }}          position={centered}        />      </React.Fragment>    );  }
  return (    <div>      <TriangleTooltip label="Notifications Really Long to Trigger Collision">        <button style={{ fontSize: 25 }}>          <span aria-hidden>🔔</span>        </button>      </TriangleTooltip>      <TriangleTooltip label="Settings">        <button style={{ fontSize: 25 }}>          <span aria-hidden>⚙️</span>        </button>      </TriangleTooltip>
      <div style={{ float: "right" }}>        <TriangleTooltip          label="Notifications Really Long to Trigger Collision"          aria-label="3 Notifications"        >          <button style={{ fontSize: 25 }}>            <span>🔔</span>            <span>3</span>          </button>        </TriangleTooltip>      </div>    </div>  );})();

Animation

Here's some code you can copy/paste to make it happen along with React Spring:

import * as React from "react";import { useTooltip, TooltipPopup } from "@reach/tooltip";import { useTransition, animated } from "@react-spring/web";
animated.TooltipPopup = animated(TooltipPopup);animated.TooltipContent = animated(TooltipPopup);
function AnimatedTooltip({ children, ...rest }) {  const [trigger, tooltip, isVisible] = useTooltip();
  const transitions = useTransition(isVisible, {    from: { opacity: 0 },    enter: { opacity: 1 },    leave: { opacity: 0 },    config: { mass: 1, tension: 500, friction: 40 },  });
  return (    <React.Fragment>      {React.cloneElement(children, trigger)}      {transitions(        (styles, item) =>          item && (            <animated.TooltipContent {...tooltip} {...rest} style={styles} />          )      )}    </React.Fragment>  );}
// Now you can replace `Tooltip` with `AnimatedTooltip`function App() {  return (    <AnimatedTooltip label="Compose Message">      <button>📝</button>    </AnimatedTooltip>  );}