Tooltip
A short description shown on hover and keyboard focus: dark by default, for labels and reasons, or a white hint card that explains something. For content someone opens and interacts with, use a Popup.
Installation
bun add @harmo-ui/css @harmo-ui/reactnpm install @harmo-ui/css @harmo-ui/reactpnpm add @harmo-ui/css @harmo-ui/reactyarn add @harmo-ui/css @harmo-ui/reactbunx harmo-ui add tooltipnpx harmo-ui add tooltippnpm dlx harmo-ui add tooltipyarn dlx harmo-ui add tooltipUsage
import { Button, Tooltip } from "@harmo-ui/react";
<Tooltip description="Download the current view as a CSV file">
<Button variant="secondary">Export</Button>
</Tooltip>Examples
Describing a control
Hover a button and its tooltip shows after 300ms, then its neighbors at once; Tab to it and it shows immediately. Escape hides it. aria-describedby reads the tooltip after the button's name.
<>
<Tooltip description="Download the current view as a CSV file">
<Button variant="secondary">Export</Button>
</Tooltip>
<Tooltip description="Hide resolved tickets from the inbox">
<Button variant="secondary">Archive</Button>
</Tooltip>
</>Naming an icon button
Without a label, a button can take its name from its tooltip through aria-labelledby, so screen readers say "Close, button" once instead of a name and then the same description.
Explaining an unavailable action
aria-disabled="true" looks disabled but stays focusable and hoverable, so the tooltip can say why. A disabled button could never show it.
<Tooltip description="You don't have permission to analyze campaigns.">
<Button aria-disabled="true">Analyze</Button>
</Tooltip><span class="htooltip-trigger">
<button class="hbutton" type="button" aria-disabled="true" aria-describedby="analyze-tip">
<span class="hbutton-label">Analyze</span>
</button>
<span class="htooltip" role="tooltip" id="analyze-tip">You don't have permission to analyze campaigns.</span>
</span>Sides
Below by default; data-side prefers another side, and the tooltip flips when there isn't room. Sides are physical: left stays left in right-to-left text.
Hint
data-variant="hint" explains something, such as a KPI: a card with smaller, start-aligned text, up to 280px wide.
<Tooltip
variant="hint"
description="Customer satisfaction: the share of rated conversations scored 4 or 5 out of 5 in the selected period."
>
<Button variant="tertiary">What is CSAT?</Button>
</Tooltip><span class="htooltip-trigger">
<button class="hbutton" data-variant="tertiary" type="button" aria-describedby="csat-tip">
<span class="hbutton-label">What is CSAT?</span>
</button>
<span class="htooltip" role="tooltip" id="csat-tip" data-variant="hint">
Customer satisfaction: the share of rated conversations scored 4 or 5 out of 5 in the selected period.
</span>
</span>Inside a clipping container
The dashed container here hides whatever overflows it. The tooltip is positioned against the window, so it still shows in full.
<Tooltip description="Fixed positioning escapes the clipping container.">
<Button variant="secondary">Hover me</Button>
</Tooltip><span class="htooltip-trigger">
<button class="hbutton" data-variant="secondary" type="button" aria-describedby="clip-tip">
<span class="hbutton-label">Hover me</span>
</button>
<span class="htooltip" role="tooltip" id="clip-tip">Fixed positioning escapes the clipping container.</span>
</span>API reference
React
<Tooltip> wraps one control, its only child, and renders the tooltip after it. Pass label to name the control, as for an icon-only button, or description to describe it; the tooltip is connected through aria-labelledby or aria-describedby to match. It hides on Escape, and neighbors show at once. Other props are the trigger <span>'s attributes.
| Prop | Effect |
|---|---|
label | The control's name |
description | The control's description |
side | top, left, or right; below when left out |
variant | hint, a white card that explains something |
HTML
A <span class="htooltip-trigger"> around the control and a <span class="htooltip"> with role="tooltip" and an id, as a direct child after the control. The control takes aria-describedby, or aria-labelledby when it names an icon-only control. Load tooltip.css.
| Hook | Effect |
|---|---|
| (default) | Below the trigger |
data-side="top", "left", or "right" | Preferred side; it flips when there isn't room. Sides are physical, as in Radix and Floating UI: left stays left in right-to-left text |
data-variant="hint" | Explains something, such as a KPI: a white card with 13px --harmo-color-text-secondary text, a border, and a soft shadow, up to 280px wide, start-aligned |
A script is required for Escape to hide it (WCAG 1.4.13), and is optional for showing neighbors at once. See Tooltip's full contract.
Accessibility
- A tooltip that describes a control is referenced with
aria-describedby, so screen readers read it after the control's name. - A tooltip that names an icon-only control is referenced with
aria-labelledbyinstead, so screen readers say "Close, button" once rather than the name and then the same description; see Button. - To explain an unavailable button, use
aria-disabled="true"rather thandisabled: adisabledbutton can't take keyboard focus, so keyboard users would never see its tooltip.