Tooltips and Popovers

yFiles for HTML provides a flexible popover system that covers everything from simple, non-interactive tooltips to fully interactive pop-up panels. All popovers are rendered in view coordinates (not scrolled with the graph) and may either be plain string content or rich HTML elements.

There are three kinds of popovers, distinguished by their PopoverBehavior:

Popover behaviors
Behavior Light-dismissable Multiple at once Description

HINT

Yes

No (only one)

Corresponds to a classic tooltip. Shows informational content without interaction. Closes automatically when clicking outside or pressing Escape.

AUTO

Yes

No (but can coexist with a Hint)

An interactive popover that can contain UI elements such as buttons. Closes automatically when clicking outside or pressing Escape.

MANUAL

No

Yes (unlimited)

The developer has full control over when and how many are shown. Not light-dismissable — a close mechanism (e.g., a close button) must be provided by the developer.

Note

Light-dismissable means a popover closes automatically when the user clicks outside of it or presses Escape. Only MANUAL popovers are not light-dismissable.

By default, all popovers are rendered in the HTML top layer and may extend beyond the component bounds. This can be changed by setting the useTopLayer to false.

Tooltips are supported by both GraphEditorInputMode and GraphViewerInputMode.

The most convenient way to provide a tooltip for a graph item is to use a handler for GraphEditorInputMode’s or GraphViewerInputMode’s query-item-tool-tip event:

// display tooltips for nodes
graphEditorInputMode.toolTipItems = GraphItemTypes.NODE
// register a listener
graphEditorInputMode.addEventListener(
  'query-item-tool-tip',
  (eventArgs) => {
    if (eventArgs.handled) {
      // A tooltip has already been assigned => nothing to do.
      return
    }
    // We can safely assume a node here because we set toolTipItems to only NODE
    const hitNode = eventArgs.item ? (eventArgs.item as INode) : null
    if (hitNode && hitNode.labels.size > 0) {
      // Setting the tooltip also sets the 'handled' property to 'true'
      eventArgs.toolTip = hitNode.labels.get(0).text
      eventArgs.handled = true // also happens automatically, when ToolTip is assigned
    }
  }
)

The toolTipItems property on GraphEditorInputMode and GraphViewerInputMode controls which items trigger the tooltip events when clicked. You can obtain a more specific distinction per item by setting a custom queryToolTipPredicate. Typically, performing the logic in the event handler is the recommended approach.

The actual work is done by the ToolTipInputMode, which is a child input mode for both GraphEditorInputMode and GraphViewerInputMode. It delegates the management of the currently open tooltips and popovers to query-item-tool-tip

Further customizations can be done on the ToolTipInputMode. GraphEditorInputMode and GraphViewerInputMode trigger the query-item-tool-tip upon ToolTipInputMode’s query-tool-tip event.

The validHoverLocationHitTestable specifies the area in which a tooltip will be queried.

The QueryItemToolTipEventArgs<TModelItem> of the query-tool-tip event allows setting the content for the tooltip and further customization of the popover through the popover property:

graphEditorInputMode.addEventListener('query-item-tool-tip', (args) => {
  if (args.item instanceof INode) {
    const node = args.item
    args.toolTip = 'Node'
    // anchor the tooltip's top center corner to the node's bottom center
    args.popover.anchor = new Point(
      node.layout.x + node.layout.width / 2,
      node.layout.bottomLeft.y
    )
    args.popover.ratios = new Point(0.5, 0) // 0.5 is the x-ratio (center), 0 is the y-ratio (top)
    // move 10 pixels below the node (in view coordinates)
    args.popover.offset = new Point(0, 10)
    // keep it open for 5 seconds, max - overrides default
    args.popover.duration = '5s'
    args.handled = true
  }
})

If the provided content on the QueryItemToolTipEventArgs<TModelItem> is a string, the library internally adds this to the parentElement as innerHTML content. While this allows for rich formatting, it creates a potential Cross-Site Scripting (XSS) vulnerability if you pass unvalidated user data into that property. So if your site uses a strict Content Security Policy, the browser will throw an error due to this injection sink.

To mitigate this, you can use the Trusted Types API by passing sanitized strings instead of pure plain strings.

For example, enabling the following CSP defines the "tooltip-policy" as a trusted type for injection sinks, disallowing any other strings:

<meta
  http-equiv="Content-Security-Policy"
  content="require-trusted-types-for 'script'; trusted-types tooltip-policy;"
/>

Then, when setting a string tooltip that triggers the library’s innerHTML assignment, you can mark it as trusted by using a tooltip-policy:

// create a trusted types policy for sanitizing tooltips
const tooltipSanitizer = trustedTypes.createPolicy('tooltip-policy', {
  createHTML: (input) => DOMPurify.sanitize(input)
})

graphEditorInputMode.addEventListener('query-item-tool-tip', (evt) => {
  if (evt.handled) return

  // the following throws due to the strict CSP and the library's innerHTML assignment
  // evt.toolTip = 'My Tooltip'

  // use the tooltip policy on the string content to mark it as secure
  evt.toolTip = tooltipSanitizer.createHTML('My Tooltip')

  // alternatively, use HTML elements for tooltips to not trigger the library's innerHTML assignment
  // const tooltip = document.createElement('span')
  // tooltip.innerText = 'My Tooltip'
  // evt.toolTip = tooltip

  evt.handled = true
})

The basic styling of the tooltip/popover element can be performed using the yfiles-tooltip or yfiles-popover CSS class. Using this CSS class, you can customize features like the background, border, margin, or font. For example, to style a tooltip with a light-gray background and a black border, you can use the following rules:

.yfiles-tooltip {
  background-color: lightgray;
  border: 2px solid black;
}
Note
The CSS animations described here apply only to tooltips / popover elements that are added to the HTML’s top-layer, i.e., when the useTopLayer is set to true (which is the default setting).

To animate popover elements that are added with the HTML Popover API, you can use the CSS specific popover pseudoclasses in combination with the .yfiles-popover__container CSS class of the popover container.

For example, to lengthen the fade-in/-out animation, you could define the following rules:

.yfiles-popover__container[popover] {
  transition:
    opacity 500ms ease,
    display 500ms allow-discrete;
}

Additionally, the popover containers for tooltips (provided by ToolTipInputMode) have the .yfiles-popover—​tooltip CSS class, while the context menu (provided by ContextMenuInputMode) uses .yfiles-popover—​context-menu. This makes it easy to adjust animations for specific popovers or for all of them at once.

Note
The CSS animation phases described here apply only to tooltips / popover elements that are not added to the HTML’s top-layer, i.e., when the useTopLayer is set to false.

You can use CSS transitions or animations to control how the tooltip element appears and disappears by leveraging different CSS classes that are applied during the enter and leave phases.

You can disable the transition or animation for either phase by not defining the related CSS classes, as explained in the following sections.

The following CSS classes are applied when the tooltip is added to the DOM:

  • yfiles-popover__container-entering: This CSS class is present during the entire enter phase and can be used to define CSS transition or animation functions. It is removed when the enter transition or animation ends.

  • yfiles-popover__container-enter: Initializes the start state of the tooltip. The class is added before the element is inserted into the DOM and removed immediately after the element is added to the DOM.

  • yfiles-popover__container-enter-to: Defines the end state of the tooltip. This class is added when yfiles-popover__container-enter is removed (i.e., immediately after the element enters the DOM), and it is removed when the CSS transition or animation ends.

By default, yFiles provides a simple fade transition for the enter phase of tooltips:

.yfiles-popover__container-entering {
  transition: opacity 0.2s ease-in;
}
.yfiles-popover__container-enter {
  opacity: 0;
}
.yfiles-popover__container-enter-to {
  opacity: 1;
}

Alternatively, you could also define a CSS animation, for example:

@keyframes fade {
  from { opacity: 0 }
  to { opacity: 1 }
}
.yfiles-popover__container-entering {
  animation: fade 0.2s ease-in;
}

The following CSS classes are applied when the tooltip leaves the DOM:

  • yfiles-popover__container-leaving: This CSS class is present during the entire leave phase and can be used to define CSS transition or animation functions. It is removed when the leave transition or animation ends.

  • yfiles-popover__container-leave: Initializes the beginning state of the tooltip as it leaves. The class is added when the leave phase begins and is removed immediately afterward, when yfiles-popover__container-leave-to is set.

  • yfiles-popover__container-leave-to: Defines the end state of the tooltip before it is removed from the DOM. This class is added when yfiles-popover__container-leave is removed and removed when the CSS transition or animation ends. The transitionend or animationend event also determines when the element is removed from the DOM.

By default, yFiles provides a simple fade transition for the leave phase of tooltips:

.yfiles-popover__container-leaving {
  transition: opacity 0.2s ease-out;
}
.yfiles-popover__container-leave {
  opacity: 1;
}
.yfiles-popover__container-leave-to {
  opacity: 0;
}

Alternatively, you could also define a CSS animation, for example:

.yfiles-popover__container-leaving {
  animation: fade reverse 0.2s ease-out;
}

You can fully customize the content displayed within a tooltip with the toolTip property. Besides plain text string, you can also assign an HTMLElement, which will be directly inserted into the tooltip’s display area. This enables embedding rich HTML structures and integrating dynamically rendered components from third-party libraries like React, Vue, or Angular.

To embed a custom component, create a container div element, render your third-party component into this container, and then assign the container element to args.toolTip within your event handler.

For concrete examples, refer to the dedicated demos: React, Angular, Vue.

Every GraphInputMode (i.e., both GraphEditorInputMode and GraphViewerInputMode) provides a popoverManager property. The PopoverManager is a utility class that manages popovers described by PopoverDescriptor instances.

The ToolTipInputMode uses the PopoverManager internally to display its tooltips as HINT popovers. Custom code can use the PopoverManager directly to open, manage, and close popovers of any behavior.

To open a popover, create a PopoverDescriptor, configure its content, behavior, and anchor, and then call open:

const descriptor = new PopoverDescriptor({
  content: 'Pop Over!',
  behavior: 'manual',
  duration: '5s',
  anchor: graphComponent.viewPoint,
  ratios: new Point(0, 0),
  offset: new Point(5, 5)
})

await graphEditorInputMode.popoverManager.open(descriptor)

The openPopovers property provides a live view of all currently open popover descriptors. The closeAll method immediately closes all popovers, including MANUAL ones.

When a new popover is opened, the PopoverManager enforces the following rules:

  • Opening a HINT popover closes any previously open Hint popover, but does not close Auto or Manual popovers.

  • Opening an AUTO popover closes any previously open Auto popover, but does not close Hint or Manual popovers. This means one Hint and one Auto popover can be visible at the same time.

  • MANUAL popovers are never implicitly closed and do not affect other popovers. Multiple Manual popovers can coexist.

Once a popover is open, conditions may change – the viewport may scroll, the user may move the mouse, or the underlying data may be updated. The PopoverManager automatically triggers a requery on each active PopoverDescriptor whenever something significant changes, such as a viewport change or pointer movement.

This requery is communicated via the update event on the PopoverDescriptor. Registered event handlers can use this event to:

  • Decide whether the popover should remain open or be closed.

  • Update the popover’s content.

  • Change the popover’s anchor position and alignment.

The event argument carries the PopoverDescriptor itself and the reason indicating why the requery was triggered. Most importantly, it provides a showPopover boolean property that listeners can set to indicate whether the popover should stay open. For HINT popovers, this defaults to false, meaning the popover will close unless a listener explicitly sets it to true.

// Listen to update events to control popover lifetime
descriptor.addEventListener('update', (args) => {
  if (args.reason === PopoverUpdateReason.VIEWPORT_CHANGED) {
    args.showPopover = true
    args.handled = true
    return
  }

  // Keep the popover open as long as the mouse is within 100px of the anchor
  const distance =
    args.queryLocation.distanceTo(descriptor.anchor!) * args.context.zoom
  args.showPopover = distance < 100
  args.handled = true

  // Optionally update the content or anchor
  // descriptor.anchor = newAnchorPosition;
})

The GraphEditorInputMode and GraphViewerInputMode provide built-in handling for tooltip requerying. By default, when the ToolTipInputMode opens a HINT popover for a graph item, the tooltip stays open and its content is not requeried as long as the mouse pointer remains over the same item.

This behavior can be changed by listening for the update event and setting showPopover to false. This closes the tooltip as soon as the pointer moves which corresponds to the old tooltip default behavior:

graphEditorInputMode.addEventListener(
  'query-item-tool-tip',
  (eventArgs) => {
    if (eventArgs.handled) {
      // A tooltip has already been assigned by another listener -> nothing to do.
      return
    }
    eventArgs.handled = true
    eventArgs.toolTip = 'Legacy ToolTip Behavior'

    // Close the tooltip for any update, e.g., pointer-moved
    eventArgs.popover.addEventListener('update', (updateArgs) => {
      updateArgs.showPopover = false
    })
  }
)

Typical use cases for AUTO and MANUAL popovers include:

  • Contextual toolbars: Displaying a toolbar with action buttons next to a selected graph element.

  • Detail panels: Showing interactive detail information – such as scrollable lists or forms – similar to a rich tooltip, but with the ability for the user to interact with the content.

  • Persistent annotations: Keeping informational panels open alongside one or more diagram elements using Manual mode.

Interactive popovers are typically opened in response to an active gesture such as clicking a graph element. For example, a developer can listen for item clicks on GraphEditorInputMode and open an Auto popover to present a contextual action menu beside the clicked node.