ui icon indicating copy to clipboard operation
ui copied to clipboard

[bug]: Cannot interact with Sonner toasts opened from Dialogs (or other modal elements)

Open arvinpoddar opened this issue 1 year ago • 13 comments

Describe the bug

If you create a sonner from within any modalic element (dialog, context menu, dropdown, popover), any interaction with the resulting sonner toast will result in the modal element closing as well.

This issue was previously described in https://github.com/shadcn-ui/ui/issues/2401. However, increasing the z-index of the sonner group does not resolve the issue, and registering custom onInteractOutside handlers for every modalic component is an unwieldy option. Ideally, one should be able to interact with the sonners while a dialog is open without closing the dialog.

Affected component/components

Sonner

How to reproduce

  1. Create a dialog
  2. Use a button in the dialog to create a sonner toast. Alternatively, enable the close button for the toast.
  3. Attempt to highlight text in the toast, or click the close button. The dialog will also close.

Codesandbox/StackBlitz link

https://codesandbox.io/p/devbox/shadcn-playground-forked-kxc7l8?file=/src/App.js:34,45&workspaceId=b7d16bab-058a-4b6f-b4f8-cb3182273009

Logs

No response

System Info

MacOS 14.4.1 (23E224), Arc Version 1.37.0

Before submitting

  • [X] I've made research efforts and searched the documentation
  • [X] I've searched for existing issues

arvinpoddar avatar Apr 11 '24 15:04 arvinpoddar

I want to work on this

Faisal-imtiyaz123 avatar Apr 13 '24 01:04 Faisal-imtiyaz123

when a dialog opens

pointer-events: none; is added to body and pointer-events: auto; is added to dialog

As a workaround I added pointer-events: auto; to the toaster

giveerr avatar Apr 13 '24 11:04 giveerr

@giveerr I was trying to figure out how does the state management occurs under the hood. How did u figure it out. Plz share.

Faisal-imtiyaz123 avatar Apr 13 '24 12:04 Faisal-imtiyaz123

@Faisal-imtiyaz123

this is a radix UI behavior

-> shadcn -> radix Dialog -> radix DismissableLayer

giveerr avatar Apr 13 '24 12:04 giveerr

when a dialog opens

pointer-events: none; is added to body and pointer-events: auto; is added to dialog

As a workaround I added pointer-events: auto; to the toaster

This still doesn't prevent the dialog from closing when the toast is interacted with, right?

arvinpoddar avatar Apr 13 '24 13:04 arvinpoddar

when a dialog opens pointer-events: none; is added to body and pointer-events: auto; is added to dialog As a workaround I added pointer-events: auto; to the toaster

This still doesn't prevent the dialog from closing when the toast is interacted with, right?

NOTE THAT THIS IS A WORKAROUND I AM CURRENTLY USING

The solution I found is this, there is already a discussion here about this

const handleClickOutside = useCallback((e: CustomEvent<{originalEvent: PointerEvent | FocusEvent }>) => {
      const targetNode = document.querySelector('.toaster')

      if (e.target && targetNode && targetNode?.contains(e.target as HTMLElement)) {
        e.preventDefault()
      }
}, [])

and add this to DialogContent

        onPointerDownOutside={handleClickOutside}
        onInteractOutside={handleClickOutside}

giveerr avatar Apr 13 '24 15:04 giveerr

@giveerr the code u have given links to pertain to radix-ui source code. How do I incorporate it in shadCn.

Faisal-imtiyaz123 avatar Apr 14 '24 01:04 Faisal-imtiyaz123

I'm also having trouble to interact with Dropdown Menu button after open a Sonner Toast. It's like the button changes the clickable space after Sonner Toast appeared in top right position. I can click and close the toast without close the dialog, but my Dialog Trigger after this has much less space to click, like, i can only click if in the top right corner of the button used as Trigger.

jessicacastro avatar May 30 '24 01:05 jessicacastro

Any updates on this bug fix?

swiftpaca avatar Jun 06 '24 16:06 swiftpaca

pointer-events: auto;

Add pointer-events: auto solve the problem

import { Toaster } from "@/components/ui/sonner"

 <Toaster position="top-right" richColors className="pointer-events-auto" />

omgcx1222 avatar Nov 13 '24 11:11 omgcx1222

Anyone still facing this issue of interacting with Sonner toast with shadcn/radix Dialog at this point? I've been trying the solutions from this thread and the other issue #2401, and still whatever I do, interacting with the toast still closes the dialog.

Edit: see comment below, it's working for me now

alexcalaunanjr avatar Dec 22 '24 19:12 alexcalaunanjr

when a dialog opens pointer-events: none; is added to body and pointer-events: auto; is added to dialog As a workaround I added pointer-events: auto; to the toaster

This still doesn't prevent the dialog from closing when the toast is interacted with, right?

NOTE THAT THIS IS A WORKAROUND I AM CURRENTLY USING

The solution I found is this, there is already a discussion here about this

const handleClickOutside = useCallback((e: CustomEvent<{originalEvent: PointerEvent | FocusEvent }>) => {
      const targetNode = document.querySelector('.toaster')

      if (e.target && targetNode && targetNode?.contains(e.target as HTMLElement)) {
        e.preventDefault()
      }
}, [])

and add this to DialogContent

        onPointerDownOutside={handleClickOutside}
        onInteractOutside={handleClickOutside}

This kinda worked for me. Instead of using .toaster as the element in the querySelector, I used [data-sonner-toast] and it works well for me

const targetNode = document.querySelector('[data-sonner-toast]')

EDIT: Since there can be multiple toasts at once, when using [data-sonner-toast], it will only work for the frontmost toast and clicking on the the others behind will still close the dialog. Here's my workaround.

  const handleClickOutside = useCallback((e: CustomEvent<{ originalEvent: PointerEvent | FocusEvent }>) => {
    const toasts = document.querySelectorAll('[data-sonner-toast]');
    const isInsideToast = Array.from(toasts).some(toast => toast.contains(e.target as HTMLElement));
    if (isInsideToast) {
      e.preventDefault();
    }
  }, []);

alexcalaunanjr avatar Dec 22 '24 19:12 alexcalaunanjr

Or even better

const handleInteractOutside = (ev: Event) => {
  // Prevent dismissing the dialog when clicking on a toast
  const isToastItem = (ev.target as Element)?.closest('[data-sonner-toaster]')
  if (isToastItem) ev.preventDefault()
}

curiosbasant avatar Mar 15 '25 18:03 curiosbasant

pointer-events: auto;

Add pointer-events: auto solve the problem

import { Toaster } from "@/components/ui/sonner"

 <Toaster position="top-right" richColors className="pointer-events-auto" />

It's working. thanks!

yunsteel avatar Mar 28 '25 03:03 yunsteel

If you want to avoid casting you can check that e.target is an instance of Element

onInteractOutside={e => {
  if (e.target instanceof Element && e.target.closest('[data-ignore-modal]') != null)
    e.preventDefault()
}}

joshuawootonn avatar May 01 '25 16:05 joshuawootonn

when a dialog opens

pointer-events: none; is added to body and pointer-events: auto; is added to dialog

As a workaround I added pointer-events: auto; to the toaster

Works for me, Thank you ❤️

polarisiota avatar Jun 24 '25 19:06 polarisiota

This issue has been automatically closed due to one year of inactivity. If you’re still experiencing a similar problem or have additional details to share, please open a new issue following our current issue template. Your updated report helps us investigate and address concerns more efficiently. Thank you for your understanding! (This is an automated message)

shadcn avatar Jul 30 '25 23:07 shadcn

Wrapping the Toaster element in a div with e.stopPropagation + adding pointer-events-auto to the toast options fixes both issues for me: it enables pointer events inside the toast while preventing them from propagating up and closing whichever modal opened the toast.

Feels cleaner than using selectors for click handling, which could select the wrong thing. Not sure if this is stupid and breaks other things, so take it with a grain of salt!

Tcintra avatar Nov 30 '25 20:11 Tcintra