DocumentationAll ComponentsContact usChangelog

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.
    • Trigger listeners:
      • By default: attached to the closest suitable parent in light DOM.
      • Customizable via:
        • trigger-host-selector to choose an ancestor.
        • triggerHostReference for 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-portal moved the tooltip out and displayed it; now, since it stays in-place and the icon has no default slot, it never appears.

Workarounds

  1. Use triggerHostReference from JS

    Bind 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;
    }
  2. 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>
  3. 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.