[NavigationMenu] Option to open navigation menu on click instead of pointer enter
Feature request
Overview
The ability to optionally have the navigation menu trigger on click instead of on hover.
Who does this impact? Who is this for?
Would love for this to be a beginner friendly boolean prop, that turns on/off click in preference to hover.
For users building more complicated navigation menus, the ability to click to open the menu and have the menu stay open can provide a better UX, as they can move their mouse into the menu more easily, without the possibility of the menu closing accidentally. In our testing it also seems that some users will automatically go to click on the trigger and close the menu without realising.
As the button primitive already has an onClick prop being passed through it could be relatively straightforward to implement with a conditional.
Additional context
For example, vercel.com (logged out) mega menus open on click. Smashing magazine article on click vs hover: https://www.smashingmagazine.com/2021/05/frustrating-design-patterns-mega-dropdown-hover-menus/#designing-a-better-mega-dropdown-tap-click-menu
Thanks for raising @hannahcancode, this is possible by preventing the relevant pointer events on trigger and content
https://codesandbox.io/s/navigation-menu-on-click-duwvgn?file=/App.js
however, this is not perfect as it relies on understanding the implementation details (which may change in the future).
I'll mark this as an improvement to explore.
Oh that's great information, thanks @andy-hook. I did dive a little into whether I could "hack" it in such a way but as you say, I didn't have the knowledge of the implementation and failed. I'll see if that helps! Would love to see it as an enhancement 😁
@andy-hook thank you so much for that code sample.
My use case: I have a horizontal menu that basically folds into an vertical accordion on smaller screens. The hover events really messed up the navigation on devices with small screens/window sizes that use an input device with hover.
Solved with something like:
export const preventHover = (event: any) => {
const e = event as Event
if (window.innerWidth < 1024) e.preventDefault()
}
…
<NavigationMenu.Trigger
onPointerMove={preventHover}
onPointerLeave={preventHover}
>
…
<NavigationMenu.Content
onPointerEnter={preventHover}
onPointerLeave={preventHover}
>
@andy-hook thank you so much for that code sample.
My use case: I have a horizontal menu that basically folds into an vertical accordion on smaller screens. The hover events really messed up the navigation on devices with small screens/window sizes that use an input device with hover.
Solved with something like:
export const preventHover = (event: any) => { const e = event as Event if (window.innerWidth < 1024) e.preventDefault() } … <NavigationMenu.Trigger onPointerMove={preventHover} onPointerLeave={preventHover} > … <NavigationMenu.Content onPointerEnter={preventHover} onPointerLeave={preventHover} >
Based on my testing, only onPointerLeave closes the menu so it's not necessary to preventHover for onPointerEnter. This should be sufficient
<NavigationMenu.Content
onPointerLeave={preventHover}
>
Hi @andy-hook! I tried using onPointerEnter and onPointerLeave and on fresh page load the navigation menu opens once with hover, then only open/close on click. How can I make it to only open on click so the behavior is consistent? Has something changed on the component behavior since this was discussed?
Hey @justkahdri, I don't think anything has changed https://codesandbox.io/p/sandbox/pensive-shadow-j8m3lm
Can you provide a sandbox?
You're right, nothing has changed. I was using onPointerEnter instead of onPointerMove and that caused the weird behavior. Realized after reading the sandbox 😅
Thanks!
Just a note, on vercel.com (which was mentioned in the OP), the "Features" menu is now opened both on hover and on click. I'm wondering if they did any sort of use study and determined this was better than opening the menu only on click.
I've found that a lot of mouse users will instinctively click the menu triggers to open them, which will close the menu as their "hover" state has already triggered the menu to open.
Here is Theo exhibiting this behaviour a few months ago.
https://github.com/radix-ui/primitives/assets/17420741/829546c6-0d92-4587-9063-4ce0fb933db3
A hack is to create mutation observers which listen to the data-state attributes of the trigger elements and prevent any onClick events within a given interval.
https://codesandbox.io/p/sandbox/peaceful-nobel-gmlbyy?file=%2FApp.tsx (codesandbox typescript is weird so this example isnt fully typesafe)
While it's a bit messy this should give the best of both click and hover worlds. If anyone can foresee any problems with this let me know!
Cheers
I've found that a lot of mouse users will instinctively click the menu triggers to open them
I find myself constantly doing that with this library.
Hi all, how do you manually control the open and close state of the navigation menu in react? Do we just set the data-state attribute to open or closed on the NavigationMenu.Trigger component? It doesn't seem to be working for me at the moment.
Hi all, how do you manually control the open and close state of the navigation menu in react? Do we just set the
data-stateattribute toopenorclosedon theNavigationMenu.Triggercomponent? It doesn't seem to be working for me at the moment.
oh nvm, i found the forceMount prop
Hi all, how do you manually control the open and close state of the navigation menu in react? Do we just set the
data-stateattribute toopenorclosedon theNavigationMenu.Triggercomponent? It doesn't seem to be working for me at the moment.oh nvm, i found the
forceMountprop
forceMount isn't how you control the open state (it's there for JS animation purpose). To control the open state, you use value and onValueChange.
@jpizzle34 @benoitgrelard can you provide an example to force an open state on a navigation item?
@jpizzle34 @benoitgrelard can you provide an example to force an open state on a navigation item?
<NavigationMenu.Root defaultValue="some-value-item">
...
</NavigationMenu.Root>
// or
const [value, setValue] = React.useState('some-value-item')
<NavigationMenu.Root value={value} onValueChange={setValue}>
...
</NavigationMenu.Root>
So there is no official way to make the menu open on the click instead of horrible hover?
Thanks for the workarounds!
I've also had to override onPointer events on the NavigationMenu.Viewport component to achieve the desired results.
Not sure why this still isn't an option without workarounds. Appreciate the tips! 👍
I tried adding these props:
onPointerEnter={preventHover}
onPointerLeave={preventHover}
On my Trigger, Content and Viewport but it still triggers the menu unfortunately.
Oddly it only triggers it on the first event. If I close the menu, it will no longer open on hover.
@jwmann by now I prevent default the following:
- on
Trigger:onPointerEnter,onPointerLeaveandonPointerMove - on
Content:onPointerEnter,onPointerLeave
The onPointerMove could make the difference. Good luck 🤞
@elbotho That definitely made the difference for me! Cheers 👍
I you as me are using a viewport element outside of the regular "flow", also add these handlers over there:
<NavigationMenu.Viewport
onPointerEnter={event => event.preventDefault()}
onPointerLeave={event => event.preventDefault()}
/>
Thanks for the workarounds!
I've also had to override
onPointerevents on theNavigationMenu.Viewportcomponent to achieve the desired results.
how? Can you point us to it?
I you as me are using a viewport element outside of the regular "flow", also add these handlers over there:
<NavigationMenu.Viewport onPointerEnter={event => event.preventDefault()} onPointerLeave={event => event.preventDefault()} />
What os the regular "flow"?
@SalahAdDin
Like this
<NavigationMenu.Viewport
onPointerEnter={event => event.preventDefault()}
onPointerLeave={event => event.preventDefault()}
/>
Glad this got added in thanks for the help!!
Hi @andy-hook! I tried using
onPointerEnterandonPointerLeaveand on fresh page load the navigation menu opens once with hover, then only open/close on click. How can I make it to only open on click so the behavior is consistent? Has something changed on the component behavior since this was discussed?
This worked for me, hope this gets added as a prop (disableHover) or something like that
<NavigationMenu.Root value={value} onValueChange={setValue}>
Can anyone explain this further? It seems like you should be able to pass in an isOpen prop, but the value prop only accepts strings...do I pass in "closed"/"open"?
Hey @kdevay, It looks like you're trying to pass isOpen as a boolean to the value prop, correct? Unfortunately, this won't work with this component. The value prop requires a string because the NavigationMenu can contain multiple items, and each needs a unique string identifier to determine which item to display.
I created a codesandbox demo hope this helps.