Errors and fixes

Shadcn: the Dialog closes when you open it from a Dropdown

The Radix menu unmounts the item and returns focus while the dialog opens. Here are three fixes, each with its own trade-off.

Key points

  • The cause is the menu lifecycle: the item that opens the dialog unmounts while the dialog mounts.
  • The cleanest fix keeps the Dialog outside the DropdownMenu, controlled by state.
  • `onSelect={(e) => e.preventDefault()}` unblocks the case but leaves the menu open under the dialog.

Short answer: the menu closes the item you clicked and unmounts the subtree that holds the dialog. Keep <Dialog> outside <DropdownMenu> and control its open state yourself: that fix leaves no side effects.

What happens, step by step

  1. You click a menu item and Radix starts closing the menu.
  2. The item unmounts and focus returns to the trigger.
  3. The dialog, a child of that item, unmounts with it.
  4. The dialog state stays open, but no node renders it any more.

The visible result is a dialog that appears for one frame and vanishes, or never appears at all.

Why the item disappears

Radix does not keep DropdownMenuContent in the DOM while the menu is closed: a presence layer mounts the content on open and unmounts it once the close finishes. A Dialog written inside a DropdownMenuItem is therefore a child of a node about to vanish. Radix closes the menu on click, returns focus to the trigger through onCloseAutoFocus and unmounts the content, so the dialog goes with it even though its state still says open.

The menu is also modal by default. While it is open, Radix applies pointer-events: none to the rest of the document and traps focus inside the content. Both behaviours are separate from the unmount, and the second one explains why some fixes appear to work and then block every click.

The clean fix: state outside the menu

"use client";
import { useState } from "react";
import { DropdownMenu, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuContent } from "@/components/ui/dropdown-menu";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";

export function RowActions() {
  const [open, setOpen] = useState(false);
  return (
    
      
        Azioni
        
           setOpen(true)}>
            Rinomina
          
        
      
      
        
          Rinomina
        
      
    
  );
}

The two components are siblings, so closing the menu does not touch the dialog. It is also the easiest version to test, because there is a single state.

The alternatives and what they cost

  • onSelect={(e) => e.preventDefault()}: stops the item from closing, but the menu stays open under the dialog and you must close it by hand.
  • <DropdownMenu modal={false}>: changes focus and scroll handling, useful when the problem is scroll locking rather than unmounting.
  • One shared state for menu and dialog: fewer nodes, but it gets harder to tell which one controls what.

The diagnostic step

 {
    // when the close comes from focus return, this fires right after the open
    console.log("dialog", next, new Error().stack);
    setOpen(next);
  }}
>

Open the dialog from the keyboard with Enter and read the sequence. If you see true followed by false in the same tick, the close comes from the focus return rather than an unmount, and the stack trace names the handler that triggered it. If onOpenChange fires once with true and the dialog never appears, the fault sits upstream, in the branch that renders it.

When the fix does not work

If the dialog still closes, the cause may be an onOpenChange that receives false from the outside focus. When the menu returns focus, Radix can read that as a click outside. Open the dialog after the menu has fully unmounted, for example with requestAnimationFrame in the handler, or with onCloseAutoFocus on the menu.

When the usual fix makes it worse

The most quoted fix is e.preventDefault() on onSelect: it stops the item from closing and leaves the menu open. With a modal menu, though, the pointer-events: none on the rest of the page stays active, so the dialog appears and ignores the mouse: buttons do not press and the text field takes no focus. The symptom shifts from the dialog closes to the dialog is unusable, which is harder to trace back to the cause. Whoever then adds modal={false} to the menu gives up the focus trap, and Tab leaves the menu while the dialog is open.

A named limitation

The documented limit of the requestAnimationFrame remedy: it has no event to attach to. It waits one frame, not the end of the menu exit animation, so with a longer animation or a slow device the dialog opens while the menu content is still mounted and the close comes back. It works, but only by luck: the dependable remedy stays moving the dialog out of the menu, where mount order no longer matters.

How to prevent it

  • One rule: no nested overlays. A dialog does not live inside a menu.
  • Test with the keyboard: open the menu, select with Enter, check that focus lands inside the dialog.
  • If the menu only launches actions, consider a Popover with static content: fewer lifecycles to coordinate.
  • Keep one state for the open flag and pass it to the dialog as a controlled prop: two sources of truth produce exactly this bug.

Where to go next

In the catalogue: dialog block with a preview, sidebar with nested menus and all blocks. The guides: the best dialog blocks, measured and accessibility checklist; the comparison: Radix, Base UI and React Aria.

Frequently asked questions

Why does the dialog open and close at once?

The menu closes its item, returns focus and unmounts the subtree that holds the dialog, so the dialog goes with it.

Is e.preventDefault() on onSelect enough?

It unblocks the common case but leaves the menu open underneath. For a modal dialog it is better to move the Dialog out of the menu.

Does modal={false} on the DropdownMenu help?

Yes when the problem is scroll locking and focus handling, but you lose the modal behaviour of the menu, so weigh the trade-off.

Does it happen with Popover too?

Yes, with any primitive that unmounts its content on close. The rule is the same: control the dialog state from outside.

How do I check the dialog gets the right focus?

Open it from the keyboard, press Tab and check that focus stays inside the dialog. Then close with Esc and check that focus returns to the menu item.