Back to Home

Issue: Radix Dialog on Mobile eats first pointer event after dismiss

  1. View the source code for this pageĀ here
  2. Open this page in a mobile browser or in Firefox with Touch Emulation enabled
  3. Tap the following button to open the Radix UI Dialog:
  4. After dismissing the dialog, try tapping the same (or a different) button again
  5. Result: The first tap after dismissing the dialog is stolen!
  6. Investigation: I discovered the following is still set on the body tag after dismiss until the next tap is received:
    <body style="pointer-events: none;">
  7. However! If I change the implementation of DialogContent from this:
  8. // from this page
    <Dialog>
      <DialogTrigger asChild>
        <button type="button">
          Tap Me, then Tap Me Again after dismissing the dialog
        </button>
      </DialogTrigger>
      <DialogContent>
        Now dismiss this dialog, then try tapping the earlier button again
      </DialogContent>
    </Dialog>
    
    // from Dialog.jsx
    export const DialogContent = React.forwardRef(({ children, ...props }, forwardedRef) => (
      <DialogContentContainer {...props} ref={forwardedRef}>
        <DialogHeaderBar>
          <CloseButton aria-label="close" asChild>
            <button type="button">
              Dismiss
            </button>
          </CloseButton>
        </DialogHeaderBar>
        <DialogContentBody>
          {children}
        </DialogContentBody>
      </DialogContentContainer>
    ))
  9. To this:
  10. // from this page
    <Dialog>
      <DialogTrigger asChild>
        <button type="button">
          Here is a second button whose pointer event also gets eaten
        </button>
      </DialogTrigger>
      <DialogPrimitive.Content style={{ /* ... */ }}>
        <DialogPrimitive.Close asChild>
          <button type="button">Dismiss</button>
        </DialogPrimitive.Close>
        Now dismiss this dialog, then try tapping the earlier button again
        </DialogPrimitive.Content>
    </Dialog>
  11. Like so:
  12. The problem goes away! The first tap after dismiss is not invalidated.
  13. Only difference is the second version swaps a React.forwardRef styled version of the Dialog Content element with the DialogPrimitive.Content element directly
  14. The curious thing is, the implementation of the first dialog is modeled almost identically to the Radix UI Design System Dialog. Compare the two Dialog implementations:
        radix-ui/design-system/.../Dialog.tsx
        just-learning-1/minimal-repros/.../Dialog.jsx
  15. I am new to coding, and I cannot for the life of me figure out why this is happening :(