ui
ui copied to clipboard
[bug]: Cannot interact with Sonner toasts opened from Dialogs (or other modal elements)
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
- Create a dialog
- Use a button in the dialog to create a sonner toast. Alternatively, enable the close button for the toast.
- 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
I want to work on this
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 I was trying to figure out how does the state management occurs under the hood. How did u figure it out. Plz share.
when a dialog opens
pointer-events: none;is added to body andpointer-events: auto;is added to dialogAs 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?
when a dialog opens
pointer-events: none;is added to body andpointer-events: auto;is added to dialog As a workaround I addedpointer-events: auto;to the toasterThis 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 the code u have given links to pertain to radix-ui source code. How do I incorporate it in shadCn.
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.
Any updates on this bug fix?
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" />
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
when a dialog opens
pointer-events: none;is added to body andpointer-events: auto;is added to dialog As a workaround I addedpointer-events: auto;to the toasterThis 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();
}
}, []);
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()
}
pointer-events: auto;
Add
pointer-events: autosolve the problemimport { Toaster } from "@/components/ui/sonner" <Toaster position="top-right" richColors className="pointer-events-auto" />
It's working. thanks!
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()
}}
when a dialog opens
pointer-events: none;is added to body andpointer-events: auto;is added to dialogAs a workaround I added
pointer-events: auto;to the toaster
Works for me, Thank you ❤️
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)
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!