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/react

Usage

import { Button, Tooltip } from "@harmo-ui/react";

<Tooltip description="Download the current view as a CSV file">
  <Button variant="secondary">Export</Button>
</Tooltip>

Examples

This site is using your theme

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.

Download the current view as a CSV fileHide resolved tickets from the inbox
<>
  <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.

CloseMore actionsAdd contact
<>
  <Tooltip label="Close">
    <Button variant="tertiary">
      <CloseIcon className="hicon" />
    </Button>
  </Tooltip>
  <Tooltip label="More actions">
    <Button variant="secondary">
      <MoreIcon className="hicon" />
    </Button>
  </Tooltip>
  <Tooltip label="Add contact">
    <Button>
      <PlusIcon className="hicon" />
    </Button>
  </Tooltip>
</>

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.

You don't have permission to analyze campaigns.
<Tooltip description="You don't have permission to analyze campaigns.">
  <Button aria-disabled="true">Analyze</Button>
</Tooltip>

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.

Above the buttonBelow the buttonTo the leftTo the right
<>
  <Tooltip description="Above the button" side="top">
    <Button variant="secondary">Top</Button>
  </Tooltip>
  <Tooltip description="Below the button">
    <Button variant="secondary">Bottom</Button>
  </Tooltip>
  <Tooltip description="To the left" side="left">
    <Button variant="secondary">Left</Button>
  </Tooltip>
  <Tooltip description="To the right" side="right">
    <Button variant="secondary">Right</Button>
  </Tooltip>
</>

Hint

data-variant="hint" explains something, such as a KPI: a card with smaller, start-aligned text, up to 280px wide.

Customer satisfaction: the share of rated conversations scored 4 or 5 out of 5 in the selected period.
<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>

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.

Fixed positioning escapes the clipping container.
<Tooltip description="Fixed positioning escapes the clipping container.">
  <Button variant="secondary">Hover me</Button>
</Tooltip>

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.

PropEffect
labelThe control's name
descriptionThe control's description
sidetop, left, or right; below when left out
varianthint, 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.

HookEffect
(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-labelledby instead, 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 than disabled: a disabled button can't take keyboard focus, so keyboard users would never see its tooltip.