[Tooltip] Tooltip is opened on active buttons when the browser tab is switched
Bug report
Current Behavior
Steps:
- Go to https://www.radix-ui.com/docs/primitives/components/tooltip
- Click on the button
- Change browser tab
- Go back to the tooltip tab
- The tooltip state is open
This happens also when the browser window is minimized/maximized.
Expected behavior
The tooltip shouldn't be open when the tab becomes active again
Reproducible example
https://www.radix-ui.com/docs/primitives/components/tooltip
Suggested solution
The issue is triggered by the onFocus handler because the browser triggers a focus event when the page becomes visible.
I've applied this workaround on my tooltips:
const prevActiveElement = useRef<EventTarget | null>(null);
useEffect(() => {
const handleFocus = (evt: Event) => {
const { target } = evt;
prevActiveElement.current = target;
};
window.addEventListener('focusin', handleFocus);
return () => {
window.removeEventListener('focusin', handleFocus);
};
}, []);
return (
<Tooltip.Root>
<Tooltip.Trigger
onFocus={(evt) => {
// Blocks the tooltip open during the page visibility change
// based on the assumption that we want to show the tooltip only when the focus
// is triggered on a new element
if (prevActiveElement.current === evt.target) {
evt.preventDefault();
}
}}
To make the example small I've placed everything in the component, but it's better to track the previous active element inside a provider.
I'm migrating from tippy.js and while they handle focus they don't have the same issue. I would also check there to find a possible solution.
Your environment
| Software | Name(s) | Version |
|---|---|---|
| Radix Package(s) | @radix-ui/react-tooltip | 1.0.2 |
| React | n/a | 18.2 |
| Browser | Chrome | 105 |
| Assistive tech | n/a | |
| Node | n/a | |
| npm/yarn | ||
| Operating System | Linux & MacOS |
Reproducing and experiencing the same issue.
are you getting a memory leak warning for this?
There are some other similar cases where this experience happens.
From looking at the code, it's anytime the trigger regains focus but the pointer is not down.
Another example case would be closing a dialog or other modal experience with the escape button. This returns focus to the trigger, but the pointer is not pressed. Alternatively, a click event only fires after the pointer up, so any button click in a dialog that closes the dialog and returns the focus will likely have this same issue.
are you getting a memory leak warning for this?
Nope
There are some other similar cases where this experience happens.
From looking at the code, it's anytime the trigger regains focus but the pointer is not down.
Another example case would be closing a dialog or other modal experience with the escape button. This returns focus to the trigger, but the pointer is not pressed. Alternatively, a click event only fires after the pointer up, so any button click in a dialog that closes the dialog and returns the focus will likely have this same issue.
Seems a different issue to me.
The one I've reported is caused by the browsers behavior of triggering a focus event when the tab/window goes back to the active state.
There are some other similar cases where this experience happens.
From looking at the code, it's anytime the trigger regains focus but the pointer is not down.
Another example case would be closing a dialog or other modal experience with the escape button. This returns focus to the trigger, but the pointer is not pressed. Alternatively, a click event only fires after the pointer up, so any button click in a dialog that closes the dialog and returns the focus will likely have this same issue.
We've experienced this behaviour as well. We commonly have Icon Buttons with Tooltips that activate Dialogs. Once the Dialog is closed, the Tooltip is shown because focus is returned back to the Icon Button. It strikes me that this is not ideal behaviour. It's a bit confusing and annoying as a user, but I will also admit I'm not intimately familiar with all the accessibility guidelines for Tooltips. Perhaps this is intended behaviour?
If it's not intended behaviour it would be useful to get a fix for it because I expect it's not uncommon to use these components together so it seems like a common issue users will run into.
As I currently understand it, it seems plausible that the issues @gdorsi and @sfrieson have described are the same root cause.
What if there was an extra check added here that checked if the target of the tooltip has focus-visible rather than focus? I'm not actually sure if there's a direct way to query focus-visible for element in javascript. I don't believe there is a focus-visible (reference: https://w3c.github.io/uievents/#event-type-focus) but you could perhaps do something along the lines of:
onFocus={composeEventHandlers(props.onFocus, (event) => {
if (!isPointerDownRef.current && document.querySelector("*:focus-visible") === event.currentTarget) context.onOpen();
})}
Same problem here, but it only happens on my end after the state of the trigger changes. It works again without any issues, after I click somewhere to hide the tooltip.
See pull request https://github.com/radix-ui/primitives/pull/2919 It would be wonderful if someone had time to review and merge