[menu] button is focused when menu is opened
Bug report
When opening a menu, I would expect that the first menu item is being focused, instead of an button.
Current behavior
When opening a menu and I have a button inside (in my case its a button which opens an explainer popover) the button is focused instead of the first menu item.
Expected behavior
When a menu is opened, the focus shifts to menu items.
Reproducible example
Here is an example codesandbox: https://codesandbox.io/p/sandbox/elegant-shaw-5hv485?file=%2Fsrc%2FApp.tsx%3A18%2C23
Base UI version
1.0.0-beta.0
Additional Context
This is only happening when opening the menu with the mouse via a click and NOT with the keyboard e.g. via arrow buttons.
This is debatable (as most things are with a11y), but imo, a menu should only contain menu items. Menu doesn't have the initialFocus prop (like Popover), because it's supposed to only contain menu items. One potential problem here is that you can't navigate to the "Open" button using the up/down arrow keys (although you can with TAB).
Ideally, I think a notifications popup thing like this would be composed with Popover (the main popup), arbitrary content (text and buttons and avatars or whatever you want), Toolbar (for a list of actions somewhere inside the popover), and our useComposite or a new RovingFocus component that we haven't introduced yet (for wrapping everything and providing roving focus between controls).
For now, depending on exactly what UI you want to build, I'd recommend using only Menu.Item, but styling your arbitrary button to look like a regular button (even though it uses Menu.Item). If you can share a pic of the exact UI you want to build, I can try to help further.
@colmtuite Thanks for the response :)
In this case I would at least expect that the behavior is consistent and doesnt change depending on whether I open the menu via mouse or keyboard..
I can't share a real picture of the UI, but I can share a sketch:
Sketch:
The whole component looks more like a select but I've used an menu here because of internal, technical reasons. The Popup has a top bar where it suggests what is being selected and a ? button which is a "helper tooltip" where all the currencies are explained in more detail and what the benefits are to choose one currency over another.
After one currency is selected its icon appears in the selects trigger, not its whole name. (The icons are also displayed in each menu item)
@KingSora The Menu pattern is for listing a bunch of actions the user can take. The Select pattern is for choosing an option from a list of options. So this UI should use Select.
It should be a Select component, with a <label>Currency</label> above it. You can use Popover with the openOnHover prop for the infotip, which should be located above the Select trigger, to the right of the "Currency" label.
@colmtuite Thanks for the explanation!
I've changed the component to a select. The behavior stays the same if I keep the infotip inside the popover though (which is the design requirement in my case)
@KingSora, were you able to find a solution to combine the menu item and the Popover?
In my case, I have a disabled Menu.CheckboxItemIndicator and a Popover that should explain why it's disabled. I tried using the:
const disabledToggleItem = (
<Menu.CheckboxItem
checked={...}
onCheckedChange={...}
disabled={...}>
Toggle smth
</Menu.CheckboxItem>
);
<Popover.Trigger
render={
// Need to wrap in a `div` to handle interactions on a disabled element.
<div aria-disabled="true">{disabledToggleItem}</div>
}
nativeButton={false}
/>
But the focus lands inconsistently either on the trigger wrapper, as I would expect, or the disabledToggleItem when opened with the keyboard.
I made a separate issue where I questioned how the Popover rendering can be handled over a disabled Menu.Item - https://github.com/mui/base-ui/issues/2693
I understand that ideally, the only focusable elements in a Menu should be the Menu.Items, but for the edge cases, it would be nice if Base UI supported it, even if not perfectly. That is, instead of focusing on the first focusable element when the menu opens regardless of what it is, it could focus on the first Menu.Item, and when pressing Shift + Tab then it could move the focus to whatever focusable element comes before the first Menu.Item, if any (if not, it would close the menu like it does now). ~~And when pressing Tab, it could do the opposite and check if there's any focusable element after the last Menu.Item, and if so, move the focus to it.~~ (oh, that already works) I realize it's probably a lot of added complexity for edge cases, though.
We need to fix support for Popover in a Menu (if the popover acts as a modal dialog it can be supported) - role="menu" can't have non-menu items though, so you should always compose the triggers with <Menu.Item> and the render prop. To prevent focus being set on them initially, adding tabIndex={-1} works
If you have complex things inside a menu that can't be represented by menuitem, menucheckboxitem or menuradioitem, you need to avoid Menu and make your UI element a Popover instead, which can contain anything inside. To retain roving tabIndex (arrow keys) behavior of the items, you can use a Toolbar inside of the Popover. It's not identical to Menu though since [data-highlighted] is no longer added.