Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No exit animation when dialog content is wrapped #3324

Open
tschai-yim opened this issue Jan 16, 2025 · 2 comments
Open

No exit animation when dialog content is wrapped #3324

tschai-yim opened this issue Jan 16, 2025 · 2 comments

Comments

@tschai-yim
Copy link

tschai-yim commented Jan 16, 2025

Bug report

Current Behavior

When wrapping the Dialog.Content component, the exit animation does not play (or is not waited for):

<Dialog.Root>
  <Dialog.Trigger>Open wrapped dialog</Dialog.Trigger>
  <Dialog.Portal>
    {/* Wrapper breaks exit animation but needed for layouting */}
    <div className="wrapper">
      <Dialog.Content>Content</Dialog.Content>
    </div>
  </Dialog.Portal>
</Dialog.Root>

Expected behavior

It should play the exit animation just like a dialog without a wrapper.

Reproducible example

https://codesandbox.io/p/sandbox/t7xqxz

Workaround

The behavior of a flex wrapper can probably be matched with a lot of CSS trickery on the dialog content. Another alternative is using another external Portal to create the wrapper:

{/* Create wrapper externally */}
<Portal ref={setWrapper} className="wrapper" />
<Dialog.Root>
  <Dialog.Trigger>Open workaround dialog</Dialog.Trigger>
  <Dialog.Portal container={wrapper}>
    <Dialog.Content className="content">Content</Dialog.Content>
  </Dialog.Portal>
</Dialog.Root>

Additional Context

The issue seems to have been around for over 2 years, as I figured out the cause through this discussion comment:
#1534 (reply in thread)

Your environment

Software Name(s) Version
Radix Package(s) @radix-ui/react-dialog 1.1.4
React n/a
Browser Firefox & Chrome 134.0 & 131.0.6778.264
Assistive tech
Node n/a
npm/yarn
Operating System Linux
@FredrikMWold
Copy link

FredrikMWold commented Jan 16, 2025

The animation breaking when the dialog structure isn’t followed is expected. However, Dialog.Content renders a div and correctly passes the className to it.

If you're using styled components or another styling method instead of CSS classes, you can use the asChild prop on Dialog.Content. This will merge all props into the first child element.

Just make sure the child component accepts a ref and forwards all props. Here’s an example:

import React from 'react';
import { forwardRef } from 'react';

const CustomContent = forwardRef(({ children, ...props }, ref) => (
  <div ref={ref} {...props}>
    {children}
  </div>
));

// Usage with Dialog.Content
<Dialog.Content asChild>
  <CustomContent>
    {/* Your content here */}
  </CustomContent>
</Dialog.Content>

This ensures the component works seamlessly with Dialog.Content and preserves animations.

@tschai-yim
Copy link
Author

Because the wrapper covers the entire screen (like an invisible backdrop), this will break the dismiss functionality when clicking outside the content because it's the size of the entire screen. I've updated the example with an Open inner wrapper dialog version to demonstrate this:

<Dialog.Root>
  <Dialog.Trigger>Open inner wrapper dialog</Dialog.Trigger>
  <Dialog.Portal>
    <Dialog.Content className="wrapper transition">
      <div className="content">Content</div>
    </Dialog.Content>
  </Dialog.Portal>
</Dialog.Root>

I need the wrapper for layouting purposes, not because of component nesting. You could probably also hack your way around with the inner wrapper, but that's probably even more fragile than the portal workaround. If the portal component needs direct access to the content child, is there a way to pass it the reference?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants