You’ve little question wrestled with menus which have nested menus earlier than. I can’t depend what number of instances I’ve hovered over a menu merchandise that reveals one other listing of menu objects, then tried to hover over that nested menu solely to have your complete menu shut on me.
That’s the setup for what I believe is a reasonably widespread concern when making menus — stopping nested menus from closing inadvertently. It’s not the customers’ fault; leaving hover between menu ranges is simple. It’s additionally not precisely the online’s fault; the menu is meant to shut if the pointer leaves the interactive space.
Earlier than we dig deeper into the difficulty, let’s acknowledge that counting on hover interactions for displaying and hiding menu objects is already considerably problematic. Not all units have a mouse, or a pointer, for that matter. You may argue that click on (or faucet) interactions are higher. Take Amazon’s major navigation for example. It requires a click on to open the principle menu and one other click on to open the nested menus.
Wanting previous a “hover versus faucet” debate, the attraction of hover interactions is apparent. It might be good to save lots of the consumer additional clicks, proper?
That’s what we’re aiming for on this article. The explanation I wish to sort out this in any respect is that I noticed Notion displaying off its new menu hover interactions.
A li’l quality-of-life replace:
Earlier than, you needed to be actually exact together with your cursor so menus wouldn’t disappear on you. Ought to really feel way more polished now 🫡 pic.twitter.com/0yTRz3CMce
— Notion (@NotionHQ) February 24, 2023
Whereas I don’t have inside details about Notion’s method, I’ve what I believe is a sensible method to go about it based mostly on different examples I’ve seen, and I wish to present you the way I received there.
The Answer: Protected Triangles
The answer is protected triangles. It’s not precisely a brand new thought, both. Amazon popularized the thought, and Ben Kamens blogged it again in 2013 whereas introducing a jQuery plugin to perform it.
The essential thought is properly illustrated on this video that Michael Villar contains in a submit on the Top app weblog.
There needs to be a “fashionable” method for including protected triangles to nested menus. I sought out extra examples.
Instance 1: VS Code
VS Code pulls off a pleasant hover interplay in its internet app.
The VS Code method makes use of a delayed set off of a mouseover
occasion callback in JavaScript. However earlier than the occasion fires, CSS kinds are utilized to the :hover
state of menu objects.
macOS
I’m on a Mac and seen that macOS additionally implements some type of protected triangle in its menus.
I can’t crack macOS open and examine its code, however this is a superb instance. Discover how the protected triangle works when transferring from a top-level menu to a nested menu however not when coming back from the nested menu to the top-level menu. It’s the type of delicate, polished distinction we would anticipate from Apple.
Radix Navigation Element
The open-source Radix library gives a Dropdown Menu element that pulls it off.
What an incredible job! I discovered this instance from a chat Vercel posted to YouTube the place Radix co-creator Pedro Duarte provides a grasp class on the design challenges of dropdown menus.
The performance is just about the identical because the macOS method, the place :hover
just isn’t triggered within the sibling components, making the expertise actually easy. Not like macOS, although, that is code that I can entry and examine.
The Radix method was massively useful so far as informing my very own work. So, let’s break down the method to point out you the way I carried out this into the principle navigation of the venture I work on, Neo4j.
Demo
I put collectively a simplified model of my implementation which you could strive. The code is written in React, however the resolution just isn’t React-specific and might be paired with any UI framework you want.
See that? The second menu exposes the “protected triangle” on hover, displaying the hoverable space that permits the nested menu to remain open, even after the pointer has left the hover state.
The way it Works
The 2 key components for this method are SVG and the CSS pointer-events
property.
Mouse Enter and Depart
First issues first. When hovering over a menu merchandise that features nested components, we set off an onMouseEnter
callback that opens the nested menu, with onMouseEnter={() => setOpen(true)}
.
Then, an onMouseLeave
callback is chargeable for closing the submenu when the pointer leaves the aspect and its youngsters by way of onMouseLeave={() => setOpen(false)}
. This half is equivalent to a easy nested menu, however discover <SafeArea />
as a result of that is the place the magic occurs.
const SafeAreaNestedOption = () => {
const [open, setOpen] = useState<boolean>(false);
const dad or mum = useRef<HTMLLIElement>(null);
const youngster = useRef<HTMLDivElement>(null);
const getTop = useCallback(() => {
const peak = youngster.present?.offsetHeight;
return peak ? `-${peak / 2 - 15}px` : 0;
}, [child]);
return (
<li
ref={dad or mum}
model={{ place: "relative" }}
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
>
<NestedPlaceholder />
{/* Protected mouse space */}
{/* That is the place the magic will occur */}
{open && dad or mum.present && youngster.present && (
<SafeArea anchor={dad or mum.present} submenu={youngster.present} />
)}
{/* Nested components as youngsters */}
<div
model={ 0,
high: getTop()
}
ref={youngster}
>
<ul>
<li>Nested Possibility 1</li>
<li>Nested Possibility 2</li>
<li>Nested Possibility 3</li>
<li>Nested Possibility 4</li>
</ul>
</div>
</li>
);
};
SVG
We use SVG to “draw” the protected triangle contained in the SafeArea
element. When a nested menu is open, we create the SVG as a toddler aspect that isn’t seen however is there. The concept is that customers work together with it when it’s uncovered, even when they don’t understand it.
The trick is to be sure that the SVG is rectangular with a peak equal to the peak of the nested menu and a width equal to the space between the cursor and the nested menu.

So long as the pointer is hovering over the SVG aspect, we have now one thing we will use to take care of the nested menu’s open state.
Pointer Occasions
There are two steps we have to take to attain this. First, we’ll create a “desired” path that connects our cursor to the submenu.
A triangular form is probably the most easy path we will assemble between a menu merchandise and a nested menu. You may visualize what this triangle may seem like within the picture beneath. The inexperienced represents the protected space, indicating that it received’t set off any onMouseLeave
occasions. Conversely, the purple space signifies that it’s going to begin the onMouseLeave
occasion since we’re possible transferring towards a sibling menu merchandise.

I approached this by making a SafeArea
element in React that accommodates the SVG markup:
<svg
model={{
place: "fastened",
width: svgWidth,
peak: submenuHeight,
pointerEvents: "none",
zIndex: 2,
high: submenuY,
left: mouseX - 2
}}
id="svg-safe-area"
>
{/* Protected Space */}
<path
pointerEvents="auto"
stroke="purple"
strokeWidth="0.4"
fill="rgb(114 140 89 / 0.3)"
d={
`M 0, ${mouseY-submenuY}
L ${svgWidth},${svgHeight}
L ${svgWidth},0
z`
}
/>
</svg>
Additionally, to consistently replace our protected triangle and place it appropriately, we’d like a mouse listener, particularly onmousemove
. I relied on a React hook from Josh Comeau known as useMousePosition
in a useMousePosition.tsx
file that gives the protected triangle element, designating the mouse place with mouseX
and mouseY
.
The Protected Triangle
The triangle is the SVG’s solely path
aspect. For this to work accurately, we should set the CSS pointer-events
property to none
, which we will do inline straight within the SVG. Then we set pointer-events
to auto
inline within the path
aspect. This fashion, we cease propagating occasions when they’re coming from the path
aspect — the protected triangle — however not when occasions come from the SVG’s purple
space.
Let’s break down the trail we’re drawing, because it’s far more easy than it appears:
<path
pointerEvents="auto"
stroke="purple"
strokeWidth="0.4"
fill="rgb(114 140 89 / 0.3)"
d={
`M 0, ${mouseY-submenuY}
L ${svgWidth},${svgHeight}
L ${svgWidth},0
z`
}
/>
We set the pointer-events
property to auto
to seize all mouse occasions, and it doesn’t set off the onMouseLeave
occasion so long as the cursor is inside the trail.
Subsequent, we offer the trail with some fundamental CSS kinds for debugging functions. This fashion, we will see the protected space whereas testing interactions.
The 0, ${mouseY-submenuY}
half is the trail’s place to begin, designating the middle of the SVG’s space.
Then we proceed our path drawing with two strains: L ${svgWidth},${svgHeight}
and L ${svgWidth},0
. The previous represents the primary line (L
) based mostly on the SVG’s width and peak, whereas the latter attracts the second line (L
) based mostly on the SVG’s width.
The z
a part of the trail is what makes the whole lot work. z
is what closes the trail, making a straight line to the trail’s place to begin, stopping the necessity to attract a 3rd line.

You may discover the trail in additional element or regulate it utilizing this SVG path editor.
There Are Some Gotchas
This can be a comparatively easy resolution on goal. There are some conditions the place this method could also be too easy, and you will have one other artistic resolution, notably for those who’re not working in React like me.
For instance, what if the consumer’s pointer strikes diagonally and touches a unique menu merchandise? This method doesn’t seize that interplay to stop the present nested menu from closing, however which may not be what you need it to do. Maybe you need the nested menu to shut and wish to regulate the SVG with a unique form. An “straightforward” method to resolve that is to debounce a cleanup perform in order that, on each mouse motion, you name the cleanup perform. And after some variety of milliseconds have handed and not using a mouse motion, you’ll take away the SVG aspect, and the sibling listeners would set off as anticipated.
One other instance is the navigation paths. A triangle is terrific however won’t be the best form on your menu and the way it’s designed. After doing a few of my very own exams, I’ve discovered {that a} curved path tends to be simpler, nearer to Needle’s method for a protected space:

Wrapping Up
As you now know, arising with an answer for nested menus that reveal on hover is extra of a problem than it appears on the floor. Whether or not a hover-based method and the clicks it saves are definitely worth the further issues that make a greater consumer expertise versus a click-based method is completely as much as you. Should you go along with a menu that depends on a mouse hover to disclose a nested menu, you now have a useful resource that enhances its usability.
What about you? Is that this a UX problem you’ve struggled with? Have you ever tried to resolve it in another way? Is the “protected triangle” idea efficient on your specific use case? I’d like to know within the feedback!

(gg, yk)