Tooltip

A short, non-interactive hint shown on hovering or focusing an element.

Installation

  1. Install the package.
npm install @hashintel/ds-components
  1. Import the component.
import { Button, Tooltip } from "@hashintel/ds-components";

export const EntityUpdatedAt = () => (
  <Tooltip content="Updated 14 Oct 2026, 09:32">
    <Button variant="subtle" iconName="clockRotateLeft">
      Updated 2 hours ago
    </Button>
  </Tooltip>
);

When to use

  • Do: Use Tooltip for brief supplementary context: the absolute time behind a relative timestamp, a keyboard shortcut, the full value behind a truncation.
  • Do: For icon-only buttons, prefer Button's tooltip prop — it renders this component and doubles as the accessible name.
  • Do: Use HelpTooltip for the info-icon-beside-a-label convention in forms and property panels.

When not to use

  • Don't: Don't put essential instructions or values in a tooltip — touch users never see one; put them in visible text such as a form Description.
  • Don't: Don't put interactive content in a tooltip — it dismisses on pointer-leave, making links and buttons inside unreachable; use Popover.
  • Don't: Don't use a tooltip for rich previews, menus, or multi-paragraph help — use Popover or Menu.

Examples

Basic

Wrap the trigger and pass content. The default tooltip is dark, placed on top, and opens after a medium delay on hover — or immediately on keyboard focus.

Variants

Tooltips are dark by default. Use light where the dark overlay loses contrast, such as over dark surfaces or imagery.

Positions

position prefers one of twelve placements — four sides, each with optional -start/-end alignment — and falls back to a better fit when the viewport cuts it off. gapX/gapY set the distance to the trigger in pixels (default 8).

Delays

openDelay sets the hover-to-open wait: none, fast, medium (the default), slow. closeDelay takes the same values for closing. Keyboard focus always opens immediately.

Disabling the tooltip

Set disableTooltip to render only the trigger — for hints that apply conditionally, such as showing a full name only while it's truncated. The wrapper also gives up its tab stop, so keyboard navigation is unaffected.

Rich content

content accepts any React node — keep it to one short line and strictly non-interactive.

import { Button, Tooltip } from "@hashintel/ds-components";

export const SearchShortcutHint = () => (
  <Tooltip
    content={
      <>
        Search entities <kbd>⌘ K</kbd>
      </>
    }
  >
    <Button variant="subtle" iconName="search" aria-label="Search entities" />
  </Tooltip>
);

Custom overlays with BaseTooltip

BaseTooltip is the unstyled primitive behind Tooltip: identical trigger behavior, positioning, and delays, but no overlay styling. Its content must be a single element that carries its own styling — it is rendered as the tooltip element itself.

import { BaseTooltip, Button } from "@hashintel/ds-components";

export const FlowRunSummary = () => (
  <BaseTooltip
    position="bottom"
    content={
      <div
        style={{
          width: 240,
          padding: 12,
          borderRadius: 8,
          background: "white",
          boxShadow: "0 8px 24px rgba(0, 0, 0, 0.16)",
        }}
      >
        <strong>Sync flow</strong>
        <p>Last run finished 12 minutes ago — 148 entities updated.</p>
      </div>
    }
  >
    <Button variant="subtle" iconName="diagramProject">
      Run summary
    </Button>
  </BaseTooltip>
);

Behavior

  • The tooltip closes on pointer-leave (after closeDelay), blur, Escape, page scroll, and clicking the trigger. Set closeOnClick={false} for triggers whose keyboard activation fires a native click — radios checked via arrow keys would otherwise dismiss the tooltip that just opened on focus.
  • When nothing inside the trigger is keyboard-focusable, the wrapper becomes a tab stop so the hint stays keyboard-reachable; it stops being one when the child becomes focusable or disableTooltip is set.
  • The menu's content mounts renders in a portal above the page, preventing most z-index + overflow issues.

Content

  • Keep content to a short phrase or sentence, under 20 words, under 10 words is even better.
  • Write sentence case. Skip the period on fragments; punctuate complete sentences.
  • Add information instead of restating: the absolute timestamp behind "2 hours ago" — never "Save" on a button labelled "Save".
  • When naming an icon-only control, use verb-first labels of 1–3 words: "Copy entity ID", "Run flow".

Accessibility

Tooltip implements the WAI-ARIA tooltip pattern.

What Tooltip provides

  • The content has role="tooltip" and is linked to the trigger with aria-describedby while open, so screen readers announce it as the trigger's description.
  • Keyboard focus opens the tooltip without delay; Escape and blur close it.
  • Triggers containing nothing focusable get a tab stop, keeping the hint reachable by keyboard.

What you must do

  • Give the trigger its own accessible name — the tooltip is a description, not a name. For icon-only buttons, prefer Button's tooltip prop, which wires both.
  • Keep content non-interactive: the tooltip dismisses on pointer-leave and blur, so links or buttons inside are unreachable — use Popover.

API reference

Tooltip

PropTypeDescription
children
ReactNode
content
ReactNode
aria-label
string
role
AriaRole
position
Position
default: "top"
disableTooltip
boolean
openDelay
"none" |⁠ "medium" |⁠ "fast" |⁠ "slow"
default: "medium"
closeDelay
"none" |⁠ "medium" |⁠ "fast" |⁠ "slow"
default: "medium"
gapX
number
default: 8
gapY
number
default: 8
closeOnClick
boolean
default: true
onOpen
(() => void)
onClose
(() => void)
variant
"light" |⁠ "dark"
default: "dark"

BaseTooltip

PropTypeDescription
children
ReactNode
content
ReactNode
aria-label
string
role
AriaRole
position
Position
default: "top"
disableTooltip
boolean
openDelay
"none" |⁠ "medium" |⁠ "fast" |⁠ "slow"
default: "medium"
closeDelay
"none" |⁠ "medium" |⁠ "fast" |⁠ "slow"
default: "medium"
gapX
number
default: 8
gapY
number
default: 8
closeOnClick
boolean
default: true
onOpen
(() => void)
onClose
(() => void)

Was this page helpful?

On this page