The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →A React dialog that vanishes the moment its open state turns false has no element left for an exit animation to play on. The fix is to keep the dialog mounted until the closing animation finishes. You can hand that job to the dialog primitive, to an animation library’s presence wrapper, to React’s ViewTransition, or to a closing state you manage yourself. The right choice depends on who owns unmounting in your stack, and the details differ by library and version.
Why the dialog disappears without animating
A pattern like {open && <Dialog />} removes the dialog node in the same commit that sets open to false. CSS can only animate an element that still exists in the DOM, so any fade-out or slide-out rule attached to the closing state never gets a frame to run. The symptom is a dialog that snaps shut even though the enter animation works.
As an Amazon Associate I earn from qualifying purchases.
Every working fix does one thing: it delays removal until the exit animation completes. The sections below cover the five common ways to do that.
Choose who owns unmounting first
Before changing code, decide which layer is responsible for keeping the dialog in the tree during the exit. The table compares the routes covered in this article.
#1 Best Overall
| Route | Who owns unmounting | Animation mechanism | Placement requirement | Key caveat |
|---|---|---|---|---|
| Radix Dialog with CSS keyframes | Radix Dialog presence handling | CSS keyframes keyed to data-state |
Standard Radix parts, no extra wrapper | Radix’s guide describes keyframe animations; do not assume a transition-only rule behaves the same |
| Radix Dialog with a JavaScript library | Your animation library, through forceMount |
JavaScript, for example React Spring | forceMount on Portal, Overlay, and Content |
You must also remove the parts after the leave animation, or they stay in the DOM |
| Motion AnimatePresence | AnimatePresence wrapper |
Motion exit prop |
Wrapper stays mounted; the animated element is its direct child | If the wrapper sits inside the condition, it cannot see the removal |
| React ViewTransition | React, during Transition updates | View Transition exit and enter animations | Only top-level ViewTransitions animate on exit and enter | Not a drop-in replacement for a dialog library’s close lifecycle; stable in React 19.3 |
| Manual closing state | Your component state | Any CSS or JavaScript | Your code controls timing | Full control, but you handle completion and fallbacks |
Also check your installed React and dialog-library versions against their current documentation before you copy a pattern. Behavior in one release may not match another.
Option 1: Radix Dialog with CSS keyframes
Radix’s animation guide says CSS animation can handle both mount and unmount. It attributes the exit behavior to Radix suspending unmount while the animation plays. The guide’s example styles the overlay and content from their data-state values, open and closed, with a 300 ms fade-in and fade-out. That duration is illustrative, not a recommended value.
- Target the overlay and content with
data-stateselectors. - Attach an opening animation to
data-state="open". - Attach a closing animation to
data-state="closed", then verify in the browser that the element stays visible until the animation ends.
.dialog-content[data-state="open"] {
animation: contentShow 300ms ease-out;
}
.dialog-content[data-state="closed"] {
animation: contentHide 300ms ease-in;
}
@keyframes contentShow {
from { opacity: 0; transform: translate(-50%, -48%) scale(0.96); }
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
@keyframes contentHide {
from { opacity: 1; transform: translate(-50%, -50%) scale(1); }
to { opacity: 0; transform: translate(-50%, -48%) scale(0.96); }
}
The guide’s example uses keyframe animations. Do not assume a plain CSS transition on data-state behaves the same way. Confirm the primitive’s lifecycle behavior before relying on a transition’s end event.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Option 2: Radix Dialog with a JavaScript animation library
JavaScript animation libraries need control of the unmount phase. Radix provides forceMount on the Portal, Overlay, and Content parts so you can delegate mounting and unmounting to your library’s animation state. The guide demonstrates this with a React Spring transition state that conditionally renders the overlay and content.
- Add
forceMountto Portal, Overlay, and Content. - Render those parts only while the library reports the element as present.
- Confirm that the leave animation’s end actually removes the rendered parts. If they stay rendered after close, the dialog remains in the DOM.
This pattern is specific to the library in the guide. Do not add forceMount mechanically. The Radix Dialog API page, which lists version 1.2.0, documents the property on each part, but whether it fits your setup depends on your library’s API.
Option 3: Motion AnimatePresence
Motion’s AnimatePresence documentation says the component enables the exit prop by detecting when its direct children are removed from the React tree. The presence component has to stay mounted above the conditional child.
Rank #3
- Place
AnimatePresencearound the conditional, not inside the component that gets removed. - Make the motion element the direct child of
AnimatePresence. - Give that element an
exitprop.
<AnimatePresence>
{open && (
<motion.div key="dialog" exit={{ opacity: 0 }}>
...
</motion.div>
)}
</AnimatePresence>
The docs also cover removal caused by a key change and by removal of list items. For lists or keyed replacements, use stable, meaningful keys. A key that changes on every render will cause unexpected removals.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Option 4: React ViewTransition
React’s ViewTransition reference says enter and exit transitions activate when a ViewTransition is added or removed. It also sets a placement constraint: only top-level ViewTransitions animate on exit and enter, and a DOM node above the ViewTransition can prevent activation.
Updates that are not marked as Transitions do not trigger these animations. State changes inside startTransition are one qualifying case. React’s React 19.3 release post, dated September 9, 2026, says ViewTransition is stable in that release.
Rank #4
- Confirm that your installed React version is 19.3 or later, and check the update path for your project.
- Make sure the ViewTransition sits at the top level, with no DOM node above it that blocks activation.
- Make sure the update that opens or closes the dialog runs inside
startTransition.
ViewTransition animates the transition itself. It does not replace the dialog library’s own close handling, so check focus and modal behavior separately.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Option 5: Manage a closing state yourself
If you do not want a library to own unmounting, track three states instead of two: open, closing, and closed.
- On a close request, set the state to
closingand keep the dialog rendered. - Apply the closing animation while the state is
closing. - When the animation completes, set the state to
closedand unmount the dialog.
Listening for animationend is the usual completion signal. It never fires if no animation runs, for example when a reduced-motion setting disables the effect or the element is hidden. Add a fallback timer that matches your animation duration so the dialog cannot get stuck in closing.
Best Value
Keep focus and interaction correct during the exit
Radix’s documentation notes that modal dialog content normally traps focus. Radix’s source code comments that focus trapping is disabled once the dialog closes, even while it remains mounted during an exit. Do not assume every dialog library behaves this way.
When you keep a dialog mounted during its exit, check these points in the browser:
- Focus returns to the element that opened the dialog once the exit finishes.
- Keyboard users cannot reach controls in a dialog that is visually closing.
- The closing element does not block clicks on the page underneath.
Troubleshooting checklist
- The dialog snaps shut. Removal happens in the same commit that sets
opentofalse, and nothing keeps the element mounted. Pick one of the routes above. - Enter animates, exit does not. The closing styles or
exitprop are missing. Add aclosedkeyframe or anexitvalue. - The dialog stays on screen after closing. With
forceMount, the parts never unmount after the leave animation. With a manual state, theclosingstate never resets. - AnimatePresence does not animate the exit. The wrapper is inside the conditional, the animated element is not a direct child, or the
exitprop is missing. - ViewTransition does nothing. The ViewTransition is not top-level, a DOM node sits above it, the update is not a Transition, or React is older than 19.3.
- A list item disappears without animating. The key is unstable or changes between renders.
Which route to choose
If you already use Radix Dialog and only need fade or scale effects, start with CSS keyframes. Use forceMount when your animation library needs to decide the timing. Use AnimatePresence when Motion already drives your animations. Choose ViewTransition only when your React version and update structure meet its requirements. Build a manual closing state when you need explicit control over completion and fallbacks.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




