Tooltip
Overview
Non-interactive description with scope coordination and safe delays.
Pattern: Tooltip. Status: stable.
When to use
- Short, non-interactive descriptions for controls that already have a primary accessible name.
- Fine-pointer hover or keyboard-focus hints where delay and one-active-tooltip-per-scope coordination matter.
When not to use
- Interactive popups, rich help panels, or content that users must tab into; use Popover or Dialog.
Import
ts
import { createTooltip } from 'ui-headless-runtime';Controller creation
Create Tooltip during component mount or setup, subscribe before rendering derived UI, and keep every cleanup returned by registrations or DOM binding.
Options
- Public options:
closeDelay,id,openDelay,positioning,scope,defaultValue,getValue,onValueChange,subscribeValue. - Pointer-type filtering is built into binding behavior; it is not a public option.
Snapshot
- Snapshot fields:
ariaDescribedby,role,triggerId,contentId,controlled,open,position,topmost. - Timer and active-scope registries are internal resources, not snapshot fields.
Commands
- Component commands:
bind,close,scheduleClose,scheduleOpen. - Tooltip opening is intentionally scheduled; there is no immediate public
opencommand.
Events
- Events:
beforeClose,beforeOpen,close,open,afterClose,afterOpen,stateChange. - Scheduling accepts hover/focus reasons, while inherited Escape and programmatic close behavior uses the shared lifecycle.
Change reasons
- Change reasons:
programmatic,trigger,escape-key,outside-pointer,focus-out,selection,context-menu,keyboard,hover,focus.
Controlled mode
Controlled tooltips can reflect design-system policies while the runtime still handles pointer/focus intent.
Uncontrolled mode
Uncontrolled tooltips own their timers and scope coordination.
DOM binding
- Bind trigger and content; apply
aria-describedbyonly while the tooltip is active.
Required markup
- Tooltip content must be concise text and must not contain focusable controls.
ARIA contract
- Use
role="tooltip"on content and connect it witharia-describedbyfrom the snapshot.
Keyboard interaction
- Focus: Open and expose aria-describedby.
- Escape: Dismiss the active tooltip.
Focus behavior
- Focus opens without hover delay; Escape closes the active tooltip and keeps focus on the trigger.
Nested behavior
- Tooltip scopes close siblings when a new tooltip opens; they do not create interactive nested overlays.
Cleanup
- Destroy clears pending open and close timers and releases scope ownership.
Minimal lifecycle example
ts
import { createTooltip } from 'ui-headless-runtime';
const controller = createTooltip();
const unsubscribe = controller.subscribe((snapshot) => {
console.log(snapshot);
});
console.log(controller.getSnapshot());
unsubscribe();
controller.destroy();The production demo loads the exact executable module from apps/demo/src/examples/tooltip.ts.
Edge cases
hover: Fine-pointer hover opens after a delay.focus: Keyboard focus opens immediately.scope: Opening one tooltip closes its sibling.
Limitations
- Touch behavior is intentionally conservative; do not depend on hover-only information for essential tasks.
Related links
API reference
See createTooltip.