Tooltip
- Source: https://github.com/reach/reach-ui/tree/main/packages/tooltip
- WAI-ARIA: https://www.w3.org/TR/wai-aria-practices-1.2/#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> );}