LiveInteractionContracts. Components. Tooltip
(live_interaction_contracts v1.0.0)
Copy Markdown
View Source
Headless, unstyled, patch-safe tooltip — popover with a hover/focus
trigger instead of a click trigger. Simpler than <.popover>: no cursor, no
aria-activedescendant, no item navigation, and (unlike popover/menu) no
client-managed ARIA reflection either, since tooltips have no expanded state.
HEEx-native anatomy (same idiom as <.popover> / <.menu>)
one function component + named slots + a single explicit
id; the component derives all wiring — trigger id,aria-describedby— from that id.
<.tooltip id="save-tip">
<:trigger>Save</:trigger>
<:content>Save this document</:content>
</.tooltip>Contract compliance (baked in — see CONTRACT_TOOLTIP.md)
- open state is browser-owned: the top layer via the native
popoverattribute, exactly like<.popover>; the server never renders or owns it, and it is never mirrored into a server-reconciled attribute - the
popoverattribute is a server-rendered constant (a client-added one would be stripped by reconciliation, KERNEL rule 1.2) role="tooltip"is a server-rendered constant on the content elementaria-describedbyon the trigger is a server-rendered constant — it always points at@id, the server never mutates it, so unlike popover'saria-expandedor menu'saria-activedescendantit needs nophx-mounted={JS.ignore_attributes([...])}protection; there is no client-managed ARIA at all on this component- there is no native declarative hover trigger, so a minimal hook
(
LicTooltip) opens the tooltip onmouseenter/focusand closes it onmouseleave/blurby callingshowPopover()/hidePopover(); the hook is only the trigger for the state change, the open state itself still lives in the top layer - content is server-rendered HEEx (the slot)
- no
aria-expanded(tooltips have no expanded state), no selection, no cursor
Usage
<.tooltip id="save-tip">
<:trigger>Save</:trigger>
<:content>Save this document</:content>
</.tooltip>Unstyled by design: bring your own classes via the slots and :rest.
Summary
Functions
Attributes
id(:string) (required) - stable id; all wiring is derived from it.mode(:string) - "manual" (default) = only the hook opens/closes it via showPopover/hidePopover; "auto" additionally participates in native light-dismiss/auto-close-siblings. Defaults to"manual". Must be one of"auto", or"manual".placement(:string) - optional zero-JS anchored positioning (CSS Anchor Positioning; see reports/POSITIONING_SPIKE_REPORT.md). Server-owned static style attrs; flips at the viewport edge; follows the anchor through patches/scroll as plain layout. Older engines show it unanchored (documented version floor; no JS fallback). Do not combine with a customstyleon the trigger or tooltip element.Defaults to
nil. Must be one ofnil,"bottom", or"top".trigger_attrs(:map) - extra attrs spread onto the trigger <button> (class, phx-click, aria-label, …). First real-app adoption finding (TASKS.md M3): icon-only action buttons want to BE the trigger, so the trigger must accept the button's own attrs. The component still owns id / aria-describedby / data-lic-tooltip / phx-hook — keys it derives are not overridable.Defaults to
%{}.Global attributes are accepted. extra attrs on the tooltip content element.
Slots
trigger(required)content(required)