Components · Overlays

Tooltip

A small floating label that appears on hover or focus. Use it for short context — a date format, a status lookup, an icon’s name. Two variants, four placements with auto-flip, an optional title + description structure for richer content.

Documentedby Derek Fidler

Hover the chip

Overview

Tooltips are short, transient. They appear on hover or focus, they carry text only, they never replace a click target. Reach for one when an icon needs a name, a date wants a longer format, or a truncated label deserves to be readable on demand. If the content is essential to the task, write it into the page instead — a tooltip is supplementary.

Hover-only is not enough

Touch users can't hover; keyboard users can't mouse. The component opens on both hover and focus so every input modality reaches the content. Mobile relies on focus alone.

Variants

Two color treatments. Default is light (white surface, hairline border) — best on dark or busy contexts. Inverse is charcoal — the everyday choice on the light surfaces most of the product lives on.

variant="default"

Light surface, charcoal text, 1 px hairline border. Use on dark or busy contexts where an inverse pill would shout.

variant="inverse"

Charcoal surface, white text. Default everywhere — reads cleanly on the light pages most of the product lives on.

Placements

Top, right, bottom, left. The arrow tracks to the trigger; the component auto-flips to the opposite side if the preferred placement would overflow the viewport.

trigger

Four placements

Hover each anchor to see the arrow track to the trigger. The component auto-flips to the opposite edge if it would overflow the viewport.

Compositions

Three composition shapes. Single-line is the default; a label + description pair handles the “Settled · 16 Jan, 19:27” look from the Figma matrix; arrowless pills slot into dense rows where neighbouring cells shouldn't see a triangle.

settled

Single line

Pass content for a one-line tooltip. Default size — Inter Tight Regular 14 / 20.

Title + description

Use label + description to mirror the Figma "Label / 16 Jan, 19:27" pattern — Inter Tight Semibold 16 / 24 over Regular 14 / 20.

No arrow

Pass showArrow={false} for an arrowless pill — used in dense rows where the arrow would clutter neighbouring cells.

Auto-flip

Hover this one — placed against the edge so the tooltip flips to the opposite side instead of overflowing.

Anatomy

Four named parts. Only the surface is required — every other piece is opt-in.

  1. Surface

    8 px radius. Inverse: #000000 charcoal fill, no border. Default: #FFFFFF with a 1 px #EEEEEF hairline.

  2. Label

    Optional title — Inter Tight Semibold 16 / 24. Use sparingly; most tooltips don't need a heading.

  3. Body

    Inter Tight Regular 14 / 20. Pass via content for a single line, or description for the line under a label.

  4. Arrow

    8 px rotated square pointing at the trigger. Auto-positions per placement. Toggle via showArrow.

Behavior

  • Hover delay, focus instant. delayMsdefaults to 400 ms — long enough to skip accidental hovers. Focus opens the tooltip immediately so keyboard users don't wait.
  • Auto-flip at the viewport edge. The component measures itself, the trigger, and the viewport. If the preferred placement overflows, it tries the opposite. No third-party positioning library — just getBoundingClientRect and a small computation.
  • Closes on Escape, scroll, resize. Escape always closes. Scroll and resize close so the tooltip never hangs in a stale position. The user re-hovers to bring it back.
  • Pointer events pass through. The tooltip sets pointer-events: none— the cursor moves over it without triggering a hover-leave on the trigger.
  • Portal rendering. The tooltip mounts into document.body so it can't be clipped by an ancestor's overflow: hidden. SSR-safe — only mounts after the client hydrates.

Accessibility

  • role="tooltip". The tooltip surface carries the role; the trigger gets aria-describedby pointing at it while open. Screen readers announce the tooltip text after the trigger's name.
  • Keyboard parity. The trigger fires onFocus, not just onMouseEnter — Tab into a tooltipped control and the content shows immediately.
  • Not for required information.A tooltip is supplementary. If the user can't complete the task without reading it, write it into the page — keyboard users on Windows High Contrast, screen-reader users with hovers off, and any user on a touch device may never see it.
  • Don't place interactive content inside. The tooltip is pointer-events: none — buttons or links inside can't be clicked. For interactive content, reach for Popover.

Code

tsx

import { Tooltip } from "@flatpay-dk/ui";

// Default — single-line content, top placement
<Tooltip content="Settled at end of day">
  <Button>Pay out</Button>
</Tooltip>

// Inverse (charcoal) — the everyday default
<Tooltip content="Cancel the run" variant="inverse">
  <IconButton aria-label="Cancel"><CrossIcon /></IconButton>
</Tooltip>

// Title + description (Figma "Label / 16 Jan, 19:27" pattern)
<Tooltip label="Settled" description="16 Jan, 19:27 · final">
  <span className="border-b border-dashed">settled</span>
</Tooltip>

// Placement override
<Tooltip content="More info" placement="right">
  <InfoIcon />
</Tooltip>

// No arrow — for dense rows
<Tooltip content="Run prototype locally" showArrow={false}>
  <Button size="sm">Run</Button>
</Tooltip>

// Custom delay
<Tooltip content="Quick reveal" delayMs={120}>
  …
</Tooltip>

// Disabled — never opens
<Tooltip content="…" disabled={!showHints}>
  <Button>…</Button>
</Tooltip>

Best practices

2 days ago

Do

Use tooltips for short, supplementary context — the icon's name, the date format behind a relative time.

Don't

Don't put essential information in a tooltip. Touch users and assistive tech may never see it.

Do

Pair tooltips with icon-only buttons — the tooltip is the button's accessible name in a visible form.

help

Don't

Don't put interactive controls inside a tooltip — pointer-events are off, the user can't click them.

Props

PropTypeDefaultDescription
contentReactNodeSingle-line tooltip content. Mutually exclusive with label + description.
labelReactNodeTitle — Inter Tight Semibold 16 / 24. Pair with description for two-line tooltips.
descriptionReactNodeBody — Inter Tight Regular 14 / 20.
placement"top" | "right" | "bottom" | "left""top"Preferred placement; auto-flips to the opposite side if it would overflow the viewport.
variant"default" | "inverse""default"default = light (white + hairline); inverse = charcoal. Reach for inverse on light pages.
delayMsnumber400Delay before opening on hover. Doesn't apply to focus — keyboard users get instant open.
showArrowbooleantrueShow the 8 px arrow pointing at the trigger.
disabledbooleanfalseWhen true, the tooltip never opens. Useful for conditional hints.
children*ReactNodeTrigger element. Wrapped in a transparent inline-flex span for event listening.