Summary
In v3.16.0 we removed internal usages of 'zui-portal' and changed how menus and tooltips are rendered: tooltips now stay inside their host component instead of being moved in the DOM. This improves stability but introduces a breaking behavior for tooltip directives placed inside components without a default slot (e.g. some <zui-icon> variants). Workarounds include using triggerHostReference / trigger-host-selector and wrapping the icon plus tooltip directive in a container where the directive is a sibling of the icon.
Details
What changed
-
Old behavior (with
zui-portal):- Menu and tooltip contents were moved or copied into a portal attached elsewhere in the DOM.
- This caused:
- Lost references to original items.
- Inconsistent, hard-to-debug behavior.
- Frequent special-case logic for new use cases.
- Limitations implementing styleguide-driven functionality.
-
New behavior (without
zui-portal):- Tooltip and menu contents remain in the component’s own DOM; only their position is managed externally.
- This keeps references intact, improving reliability and predictability.
- The public API is kept, with some props marked deprecated.
- Internal structure changed; any tests or custom logic that relied on the portal-based structure may break and need updates.
Tooltip directive behavior and limitation
-
Implementation highlights:
<zui-tooltip-directive>:- Renders its slotted content via
<zui-overlay-directive>. - Uses Floating UI to position a native HTML popover.
- Renders its slotted content via
- Trigger listeners:
- By default: attached to the closest suitable parent in light DOM.
- Customizable via:
trigger-host-selectorto choose an ancestor.triggerHostReferencefor direct references (e.g. inside shadow DOM).
-
Breaking behavior: components without a default slot
- If the directive is used within a web component that does not expose a default slot, the tooltip content is not rendered, because the parent component ignores children.
- Example that no longer works:
<zui-icon-alerts-ewi-info> <zui-tooltip-directive trigger="focus,hover"> <zui-tooltip>Some tooltip text</zui-tooltip> </zui-tooltip-directive> </zui-icon-alerts-ewi-info> - Previously,
zui-portalmoved the tooltip out and displayed it; now, since it stays in-place and the icon has no default slot, it never appears.
Workarounds
-
Use
triggerHostReferencefrom JSBind the tooltip directive to the icon as its trigger:
const icon = document.querySelector('zui-icon-alerts-ewi-info'); const tooltip = document.querySelector('zui-tooltip-directive'); if (icon && tooltip) { tooltip.triggerHostReference = icon; } -
Use
trigger-host-selector(when applicable)Attach listeners to a specific ancestor in light DOM:
<div class="icon-wrapper"> <zui-icon-alerts-ewi-info aria-describedby="icon-tooltip"></zui-icon-alerts-ewi-info> <zui-tooltip-directive id="icon-tooltip" trigger="focus,hover" trigger-host-selector=".icon-wrapper" > <zui-tooltip>Some tooltip text</zui-tooltip> </zui-tooltip-directive> </div> -
put the tooltip directive next to the icon in a container
Instead of placing the directive inside the icon (which lacks a default slot), wrap both in a container and use that container (or the icon) as trigger:
<div class="icon-with-tooltip" aria-describedby="icon-tooltip"> <zui-icon-alerts-ewi-info></zui-icon-alerts-ewi-info> <zui-tooltip-directive id="icon-tooltip" trigger="focus,hover" trigger-host-selector=".icon-with-tooltip" > <zui-tooltip>Some tooltip text</zui-tooltip> </zui-tooltip-directive> </div>- The container:
- Wraps the icon and tooltip directive.
- Can be sized to “fit the content” (e.g. via CSS) so the interactive area matches the icon.
- Acts as a stable host for trigger handling, avoiding issues with components without slots.
- The container: