UPopover dismisses immediately when opened programmatically after a UDropdownMenu item selection
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 6.9k
- Forks
- 1.1k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 57
Description
Environment
- macos v26.3
- node version: v22.22.0
- pnpm version: 10.33.2
Is this bug related to Nuxt or Vue?
Nuxt
Package
v4.x
Version
4.7.1
Reproduction
Description
UPopover dismisses immediately when opened programmatically after a UDropdownMenu item selection
Description
When a UPopover is opened programmatically (via v-model:open) inside the onSelect callback of a UDropdownMenu item, the popover briefly mounts and then dismisses itself.
The dismissal happens after the dropdown's exit animation completes (a few hundred ms later), which makes the popover appear to flash open and immediately close.
Reproduction
A list of programmatically-controlled popovers, plus a dropdown menu whose items add new entries to that list and open the corresponding popover.
<script setup>
const filters = ref([])
const openedPopovers = reactive({})
const items = [
{ label: 'Status', onSelect: () => addFilter('status') },
{ label: 'Country', onSelect: () => addFilter('country') }
]
function addFilter(key) {
filters.value.push({ key, value: null })
openedPopovers[key] = true
}
</script>
<template>
<div class="flex gap-2">
<UPopover
v-for="filter in filters"
:key="filter.key"
v-model:open="openedPopovers[filter.key]"
>
<UButton :label="filter.key" />
<template #content>
<div class="p-4">Popover content for {{ filter.key }}</div>
</template>
</UPopover>
<UDropdownMenu :items="items">
<UButton label="Add filter" />
</UDropdownMenu>
</div>
</template>
Expected behavior
The newly added popover opens and stays open until the user explicitly dismisses it (click outside, escape, Tab out).
Actual behavior
The popover mounts and renders its content, then dismisses itself ~200ms later (matching the dropdown's exit animation duration). To the user, it looks like the popover "flashes" open.
Root cause
The dismissal chain, captured via console.trace on update:open (false):
handleAnimationEnd (reka-ui/Presence/usePresence)
-> state machine dispatch
-> Vue scheduler patch
-> unmount FocusScope, MenuContent, etc.
-> FocusScope cleanupFn fires unmountAutoFocus
-> DropdownMenu.Content.handleCloseAutoFocus
-> setTimeout(0) -> rootContext.triggerElement.focus()
-> focusin on dropdown trigger (outside popover layer)
-> Popover DismissableLayer.handleFocus (async, awaits nextTick x2)
-> dispatch focusOutside event
-> emit dismiss
-> PopoverContentImpl.onDismiss
-> PopoverRoot.onOpenChange(false)
-> emit update:open(false)
-> popover closes
In sequence:
addFiltersetsopenedPopovers[key] = true. The popover mounts and itsDismissableLayerregisters afocusinlistener on the document.- The dropdown closes with an exit animation (
usePresence). After the animation ends, the dropdown content unmounts. - During unmount, the dropdown's
FocusScopefiresunmountAutoFocus. Reka'sDropdownMenuContent.handleCloseAutoFocusschedules asetTimeout(0)that focuses the dropdown's trigger element (intended a11y behavior — returns focus to the trigger after the menu closes). - The
focus()call firesfocusinon the document. The newly mounted popover'suseFocusOutsidelistener sees the target (the dropdown trigger) is outside its layer and emitsdismiss. dismisspropagates toPopoverRoot.onOpenChange(false)and the popover closes.
This is a timing collision between two correct, independent a11y behaviors:
DropdownMenureturning focus to its trigger on close.Popoverdismissing itself when focus moves outside its layer.
When the second popover opens during the first menu's close cycle, the first menu's focus restoration is interpreted as a focus-out-of-popover and dismisses it.
This is not specific to the dynamic component inside the popover. The dismiss happens whatever the popover renders. It just becomes more visible with content that does not synchronously grab focus (e.g. USelect/USelectMenu, whose autofocus is setTimeout-based, vs UInput which can mask the issue when the input is focused fast enough to set isFocusInsideDOMTree=true before the dropdown's setTimeout fires).
Workarounds
Two workarounds, both with tradeoffs:
1. Prevent the dropdown from restoring focus to its trigger
<UDropdownMenu
:content="{ onCloseAutoFocus: event => event.preventDefault() }"
:items="items"
>
Skips the chain at step 3 — the trigger never gets focused, so no focusin outside the popover.
Tradeoff: keyboard users lose the focus-return-to-trigger when closing the dropdown via Escape or by clicking outside. The reliability of this workaround also depends on the order in which Vue runs the multiple closeAutoFocus listeners (forwarded emit vs. internal handler); the internal handler may run first and schedule the setTimeout before our preventDefault is reached.
2. Prevent the popover from dismissing on focus-outside
<UPopover
v-model:open="openedPopovers[key]"
:content="{ onFocusOutside: event => event.preventDefault() }"
>
Skips the chain at step 4 — focusOutside is prevented, no dismiss.
Tradeoff: the popover no longer closes when focus moves outside via Tab. pointerDownOutside and Escape still work, so this is acceptable for most uses but does break the standard keyboard-dismiss expectation.
Suggestion
It would be ideal if either:
UDropdownMenu/Reka DropdownMenudeferred itscloseAutoFocussetTimeoutonly when no other dismissable layer has been mounted in the meantime, orUPopover/Reka PopoverignoredfocusOutsideevents whose target is aDropdownMenutrigger that was open at the moment the popover was created.
Even just documenting the interaction and exposing a clean way to suppress the focus restoration (without losing the a11y of trigger refocus on user-driven close) would help.
Environment
- Nuxt UI: 4.7.1
- Reka UI: 2.9.6
- Vue: 3.5.34
- Nuxt: 4.x
Additional context
No response
Logs
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the UPopover and UDropdownMenu focus and dismissal entry points described in the issue, then reproduce the behavior using the linked playground. Trace the dropdown's close-auto-focus sequence alongside the popover's focus-outside handling. Done means a programmatically opened popover remains open after the menu closes while normal keyboard focus restoration and dismissal behavior are preserved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- nuxt, typescript
- Domain
- accessibility, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100