Nested submenus break when window loses focus
Describe the bug
Nested <sl-menu> submenus do not close while the browser window does not have focus. This results in:
- Multiple sibling submenus opening at once
- Submenus remaining expanded until the window regains focus and a focusout event is triggered
- Submenu anchor positioning becoming incorrect (submenus reappear at random locations) after reopening a parent menu
The underlying issue appears to be that the submenu controller (SubmenuController) relies heavily on focusout and related focus events to manage closing behavior. These events do not fire when the browser window itself does not have focus, making submenu state inconsistent when mouseover events fire until the window regains focus.
To Reproduce
- Create a menu with one or more nested submenus using
<sl-menu>and<sl-menu-item>withslot="submenu". - Open a menu that has multiple submenus.
- While the submenu is open, click outside the browser window or Alt+Tab so the browser loses focus.
- While the browser window is still unfocused, move the mouse over other menu items with submenus (without clicking).
- Observe that:
- Multiple submenus open simultaneously.
- They do not collapse when moving the mouse away.
- Refocus the browser window, hover the anchor menu item, then move the mouse away.
- Only at this point will
focusoutfire and collapse the submenu(s).
- Only at this point will
- If the parent menu collapsed while submenus were “half-open”, re-open the parent menu.
- The submenus will re-render, but they appear anchored at incorrect positions on the page.
Demo
https://jsfiddle.net/psy23xf1/
Screenshots
Browser / OS
OS: Windows 10 Browsers tested:
- Microsoft Edge 142.0.3595.80
- Firefox 145.0
- Chrome 142.0.7444.163
This issue is a code logic issue, so browser/OS should have no influence on reproducibility.
Additional information
Key observations:
- The bug occurs while the browser window is unfocused, not when it regains focus, but polluted states will remain even after the browser regains focus until events trigger that clean them up.
- Because focusout does not fire when the window itself loses focus, the submenu controller never receives its signal to collapse submenus.
- While unfocused, the browser still fires mouseover events when the user moves the mouse over the page, causing submenus to open without the corresponding collapse logic.
- When the window regains focus, the submenu state can recover, but only after hovering and exiting the anchor menu item.
- If a parent menu is reopened after submenus were left in this inconsistent state, the submenu popups re-render using stale geometry and appear anchored incorrectly.
This results in incorrect submenu state, broken hover/focus styling, and misaligned submenu popups.
A potential fix may involve one or more of the following:
- Ensuring only one submenu at a given depth can be expanded at once.
- Resetting submenu state when the window loses focus or when the document visibility changes.
- Providing a public API method to fully close a submenu (popup, hover/focus state, and component update) in a consistent manner.
I have resolve the bug in my local environment using the following event handler:
function closeSubmenu(menuItem) {
const submenuPopup = menuItem.renderRoot?.querySelector('sl-popup');
if (submenuPopup && submenuPopup.active) {
submenuPopup.active = false;
}
menuItem.blur();
menuItem.requestUpdate();
}
document.addEventListener('sl-reposition', event => {
const menuItem = event.target;
if (menuItem?.tagName !== 'SL-MENU-ITEM') return;
const parentMenu = menuItem.closest('sl-menu');
if (!parentMenu) return;
parentMenu.querySelectorAll('sl-menu-item').forEach(item => {
if (item === menuItem) return;
closeSubmenu(item);
});
});it's not a perfect solution, however, as the UI seems to not react quite in the same way as when the window has focus.
Source: shoelace-style/shoelace