Tooltip
hc-tooltip styles a popover element as a small label that
describes its trigger. The trigger references the tooltip via
aria-describedby so screen readers announce the description when
the trigger receives focus; installTooltip() then handles the
visual show / hide on hover, focus, and Escape.
Browser baseline
Section titled “Browser baseline”| Primitive | Required version |
|---|---|
HTML popover | Chrome 114, Edge 114, Firefox 125, Safari 17 |
| CSS Anchor Positioning | Chrome 125, Edge 125, Firefox 147, Safari 26 |
Browsers without Anchor Positioning fall back to a
getBoundingClientRect-based positioning hook so the tooltip still
appears above the trigger. Tooltips remain functional everywhere
popover is supported.
We use popover="manual" rather than the newer
popover="hint"
because Safari had no hint support as of 2026-05. manual + JS
toggling matches the hint semantics — separate tooltips coexist
without dismissing each other — in every browser that supports
popover at all.
Basic example
Section titled “Basic example”<button class="hc-button" type="button" aria-describedby="save-tip"> <span aria-hidden="true">💾</span> <span>Save</span></button><div class="hc-tooltip" id="save-tip">Save document (Ctrl+S)</div>
<button class="hc-button" type="button" aria-describedby="delete-tip" data-variant="error"> <span aria-hidden="true">🗑️</span> <span>Delete</span></button><div class="hc-tooltip" id="delete-tip">Move to trash</div>import { installTooltip } from '@hypermedia-components/core';installTooltip();What installTooltip() does
Section titled “What installTooltip() does”- Auto-attributes the tooltip element if the author has not:
popover="manual"(full JS control of show / hide) androle="tooltip"(screen-reader semantics). - Wires every trigger that references the tooltip via
aria-describedby— one tooltip can serve multiple triggers. - Injects matching
anchor-name(on the active trigger) andposition-anchor(on the tooltip) so the tooltip lands above the trigger via CSS Anchor Positioning. Re-binds the anchor when a different shared trigger is hovered / focused. - Show / hide timing:
mouseenter→ show after a 300 ms delay (industry-standard “intent to hover” threshold).mouseleave→ hide after a 100 ms grace period (cancels a pending show if it fires during the delay).focus→ show immediately (no delay for keyboard users — APG guidance).blur→ hide immediately.Escapewhile focused → hide without moving focus.
Placement
Section titled “Placement”The tooltip sits above the trigger by default. Override it per instance
with data-side (top / right / bottom / left) and optional
data-align (start / center / end, default center). Add data-arrow
for a small pointer.
<button aria-describedby="hint">Help</button><div class="hc-tooltip" id="hint" data-side="right" data-arrow> Appears to the right</div>Placement uses CSS Anchor Positioning (position-area) where supported and
the same JS fallback as the default placement otherwise — data-side /
data-align drive both paths identically. The shared mechanics — both
paths, the arrow, and the --hc-anchored-* offset / arrow knobs — are
documented in
Fundamentals → Anchored positioning;
the mechanism reached Baseline in 2026 with a JS fallback for older
engines.
Use a real description, not the title attribute
Section titled “Use a real description, not the title attribute”The browser’s native tooltip rendered from the title attribute
cannot be styled, behaves inconsistently across platforms, and does
not appear on touch devices. Prefer the hc-tooltip +
aria-describedby pattern. If you must keep a title, the behavior
won’t conflict — but the browser will render its own tooltip on top
of yours, so remove it.
Accessibility
Section titled “Accessibility”- Always reference the tooltip from its trigger via
aria-describedby="<tooltip-id>". Screen readers will announce the tooltip text when the trigger gains focus, regardless of whether the visual tooltip is currently rendered. - Tooltips are for short text labels only. If you need
interactive content (buttons, form fields), use
Popover or
Dialog instead. The
tooltip surface is
pointer-events: noneso it cannot intercept clicks. - The tooltip must remain reachable while the trigger keeps focus —
do not hide it on a timer. The behavior does not auto-dismiss
while focus is held; only
Escape,blur, ormouseleaveclose it. - Touch devices have no hover event. The behavior does not poly-fill long-press → show; design with the assumption that the trigger’s accessible name carries the essential meaning, and the tooltip augments it.
Theming tokens
Section titled “Theming tokens”Component tokens (in component.tokens.json):
| Token path | Purpose |
|---|---|
tooltip.bg / fg | Surface and text color. Defaults to dark on light per the convention. |
tooltip.radius | Corner radius. |
tooltip.padding-x / padding-y | Inner padding. |
tooltip.font-size | Text size. |
tooltip.max-width | Cap before wrapping. |
tooltip.offset | Distance between the trigger and the tooltip. |
CSS variables
Section titled “CSS variables”Show the generated CSS variables
--hc-tooltip-bg | -fg | -radius--hc-tooltip-padding-x | -padding-y | -font-size--hc-tooltip-max-width | -offset