Reported by
Found while reviewing #9992.
Describe the bug
When an EuiToolTip wraps an element that is semantically disabled via aria-disabled (i.e. an EUI button with isDisabled + hasAriaDisabled) rather than the native disabled attribute, the tooltip appears on hover but never dismisses on mouse-out. It stays on screen until something else forces it to unmount.
euiToolTipAnchor neutralises pointer events only for natively disabled elements:
https://github.com/elastic/eui/blob/main/packages/eui/src/components/tool_tip/tool_tip.styles.ts#L86-L97
*[disabled] {
pointer-events: none;
}
That rule exists precisely to prevent this failure β its comment reads "Disabled elements don't fire mouse events, which means leaving a disabled element wouldn't trigger the onMouseOut and hide the tooltip."
hasAriaDisabled swaps disabled for aria-disabled="true", so the selector stops matching and pointer-events stays auto. The element then becomes a live hit-test target, and useEuiDisabledElement's capture-phase listeners call stopImmediatePropagation()/stopPropagation() on mouseout/mouseover. React delegates those at the root to synthesise onMouseEnter/onMouseLeave, so the anchor's onMouseLeave β hideToolTip() never runs.
Impact and severity
Moderate. A stale tooltip can obscure adjacent UI until the user interacts elsewhere. Keyboard focus/blur is unaffected β this is mouse-only.
These call sites already pair isDisabled with hasAriaDisabled inside a tooltip and should all be affected:
packages/eui/src/components/basic_table/default_item_action.tsx
packages/eui/src/components/basic_table/collapsed_item_actions.tsx
packages/eui/src/components/context_menu/context_menu_item.tsx
packages/eui/src/components/button/button_group/button_group.tsx
packages/eui/src/components/list_item_layout/_list_item_layout.tsx
packages/eui/src/components/date_picker/auto_refresh/auto_refresh.tsx
Workaround: apply pointer-events: none to the disabled element yourself, which is what the tooltip docs already advise for custom elements.
Environment and versions
To Reproduce
<EuiToolTip content="Insufficient permissions">
<EuiButtonIcon iconType="lock" aria-label="Locked" isDisabled hasAriaDisabled />
</EuiToolTip>
- Move the pointer onto the button β the tooltip appears.
- Move the pointer away.
- The tooltip remains visible indefinitely.
Note: the pointer must move across the element in steps. Instantly warping the cursor (as some automated tests do) will not generate the intermediate mouseover and can mask the bug.
Expected behavior
The tooltip should hide on mouse-out, matching the behavior of a natively disabled anchor.
Additional context
Verified fix β extending the existing rule restores mouse-out dismissal while leaving keyboard focus/blur working:
- *[disabled] {
+ *[disabled],
+ *[aria-disabled='true'] {
pointer-events: none;
}
A/B measured in-browser on the same story:
|
hover on |
hover off |
pointer-events: auto (current) |
tooltip shown |
tooltip still shown |
pointer-events: none (fix) |
tooltip shown |
dismissed |
This also aligns the component with its own documentation, which already tells consumers to set pointer-events: none on aria-disabled elements:
https://github.com/elastic/eui/blob/main/packages/website/docs/components/display/tooltip.mdx#L138
Existing coverage misses this because tool_tip.spec.tsx fires synthetic events directly at the anchor wrapper, bypassing the child entirely:
https://github.com/elastic/eui/blob/main/packages/eui/src/components/tool_tip/tool_tip.spec.tsx#L59-L79
A regression test should use cy.realHover() on the child element and then move the pointer away.
Reported by
Found while reviewing #9992.
Describe the bug
When an
EuiToolTipwraps an element that is semantically disabled viaaria-disabled(i.e. an EUI button withisDisabled+hasAriaDisabled) rather than the nativedisabledattribute, the tooltip appears on hover but never dismisses on mouse-out. It stays on screen until something else forces it to unmount.euiToolTipAnchorneutralises pointer events only for natively disabled elements:https://github.com/elastic/eui/blob/main/packages/eui/src/components/tool_tip/tool_tip.styles.ts#L86-L97
That rule exists precisely to prevent this failure β its comment reads "Disabled elements don't fire mouse events, which means leaving a disabled element wouldn't trigger the onMouseOut and hide the tooltip."
hasAriaDisabledswapsdisabledforaria-disabled="true", so the selector stops matching andpointer-eventsstaysauto. The element then becomes a live hit-test target, anduseEuiDisabledElement's capture-phase listeners callstopImmediatePropagation()/stopPropagation()onmouseout/mouseover. React delegates those at the root to synthesiseonMouseEnter/onMouseLeave, so the anchor'sonMouseLeaveβhideToolTip()never runs.Impact and severity
Moderate. A stale tooltip can obscure adjacent UI until the user interacts elsewhere. Keyboard focus/blur is unaffected β this is mouse-only.
These call sites already pair
isDisabledwithhasAriaDisabledinside a tooltip and should all be affected:packages/eui/src/components/basic_table/default_item_action.tsxpackages/eui/src/components/basic_table/collapsed_item_actions.tsxpackages/eui/src/components/context_menu/context_menu_item.tsxpackages/eui/src/components/button/button_group/button_group.tsxpackages/eui/src/components/list_item_layout/_list_item_layout.tsxpackages/eui/src/components/date_picker/auto_refresh/auto_refresh.tsxWorkaround: apply
pointer-events: noneto the disabled element yourself, which is what the tooltip docs already advise for custom elements.Environment and versions
main(the affected style rule and hook are both onmain; reproduced against the branch in [Flyout Menu] expand props for menu actionsΒ #9992, which newly surfaces it on flyout menu actions)To Reproduce
Note: the pointer must move across the element in steps. Instantly warping the cursor (as some automated tests do) will not generate the intermediate
mouseoverand can mask the bug.Expected behavior
The tooltip should hide on mouse-out, matching the behavior of a natively
disabledanchor.Additional context
Verified fix β extending the existing rule restores mouse-out dismissal while leaving keyboard focus/blur working:
A/B measured in-browser on the same story:
pointer-events: auto(current)pointer-events: none(fix)This also aligns the component with its own documentation, which already tells consumers to set
pointer-events: noneonaria-disabledelements:https://github.com/elastic/eui/blob/main/packages/website/docs/components/display/tooltip.mdx#L138
Existing coverage misses this because
tool_tip.spec.tsxfires synthetic events directly at the anchor wrapper, bypassing the child entirely:https://github.com/elastic/eui/blob/main/packages/eui/src/components/tool_tip/tool_tip.spec.tsx#L59-L79
A regression test should use
cy.realHover()on the child element and then move the pointer away.