1. Read the FAQs 👇
Read. All immediate children of AnimatePresence have a stable, unique key, and AnimatePresence itself is never unmounted.
2. Describe the bug
With mode="wait", when the key of the child changes repeatedly at an interval close to the exit duration (i.e. a new key arrives right around the moment the previous child finishes its exit animation), AnimatePresence permanently stops rendering the latest child. The stale child stays on screen forever — waiting does not recover it, and subsequent key changes don't either.
From reading the source, it looks like the exiting child's entry in the exitComplete map can stay false forever in this timing window, so isEveryExitComplete never becomes true and setRenderedChildren(pendingPresentChildren.current) is never called again.
This is essentially the same symptom as #2023 (closed). The changelog claims related fixes in 12.33.1 ("Ensure exiting nodes are correctly removed when rapidly switching children") and 12.36.0 ("Prevent mode='wait' elements from getting stuck when switched rapidly"), but the reproduction below still triggers on 12.26.2, 12.43.0 and 13.1.1 (latest).
We originally hit this in production with an animated running total next to a charge amount input: typing quickly desynced the displayed total from the actual value permanently (reproduced on iOS Safari, iOS/Android WebView and Android Chrome).
3. IMPORTANT: Provide a CodeSandbox reproduction of the bug
Repository: https://github.com/re-taro/reproduction_framer-motion_animate-presence-wait-stuck
The reproduction is fully automated: press the "Reproduce" button. It increments a keyed counter 20 times per burst, sweeping update intervals from 16ms to 96ms (exit duration is 75ms), for up to 5 rounds. After each burst it waits 1.5s (far longer than exit 75ms + enter 150ms) and compares the rendered text with the actual state, stopping at the first desync. The three framer-motion versions are installed side-by-side via pnpm aliases and can be switched with ?v=12.26.2 / ?v=12.43.0 / ?v=13.1.1.
4. Steps to reproduce
- Open the reproduction and press "Reproduce"
- Wait for the sweep to finish (it stops at the first desync)
- Observe a log line like
round=1 interval=80ms: rendered="104" state="120" << BUG: stuck forever
- Note that the rendered value never catches up, no matter how long you wait
5. Expected behavior
After updates stop, the last committed child (the latest key) should always end up rendered, even if intermediate children were skipped while waiting for exit animations.
6. Video or screenshots
Results from the automated sweep (2026-09-02, Chromium 148 / React 18.3.1 / Vite 8.2.2, macOS):
| framer-motion |
result |
| 12.26.2 |
round=1 interval=16ms: rendered="6" state="20" << BUG: stuck forever |
| 12.43.0 |
round=1 interval=80ms: rendered="104" state="120" << BUG: stuck forever |
| 13.1.1 (latest) |
round=1 interval=80ms: rendered="107" state="120" << BUG: stuck forever |
Because the race is timing-dependent, the interval that triggers it varies between runs and machines; the sweep usually catches it within the first round.
7. Environment details
- macOS 26.6.2 / Chromium 148 (also reproduced on iOS Safari, iOS/Android WebView, Android Chrome in production)
- React 18.3.1, react-dom 18.3.1
- framer-motion 12.26.2 / 12.43.0 / 13.1.1, imported directly (no
motion wrapper involved)
- Vite 8.2.2, production and development builds both reproduce
1. Read the FAQs 👇
Read. All immediate children of
AnimatePresencehave a stable, uniquekey, andAnimatePresenceitself is never unmounted.2. Describe the bug
With
mode="wait", when thekeyof the child changes repeatedly at an interval close to the exit duration (i.e. a new key arrives right around the moment the previous child finishes its exit animation),AnimatePresencepermanently stops rendering the latest child. The stale child stays on screen forever — waiting does not recover it, and subsequent key changes don't either.From reading the source, it looks like the exiting child's entry in the
exitCompletemap can stayfalseforever in this timing window, soisEveryExitCompletenever becomes true andsetRenderedChildren(pendingPresentChildren.current)is never called again.This is essentially the same symptom as #2023 (closed). The changelog claims related fixes in 12.33.1 ("Ensure exiting nodes are correctly removed when rapidly switching children") and 12.36.0 ("Prevent mode='wait' elements from getting stuck when switched rapidly"), but the reproduction below still triggers on 12.26.2, 12.43.0 and 13.1.1 (latest).
We originally hit this in production with an animated running total next to a charge amount input: typing quickly desynced the displayed total from the actual value permanently (reproduced on iOS Safari, iOS/Android WebView and Android Chrome).
3. IMPORTANT: Provide a CodeSandbox reproduction of the bug
Repository: https://github.com/re-taro/reproduction_framer-motion_animate-presence-wait-stuck
The reproduction is fully automated: press the "Reproduce" button. It increments a keyed counter 20 times per burst, sweeping update intervals from 16ms to 96ms (exit duration is 75ms), for up to 5 rounds. After each burst it waits 1.5s (far longer than exit 75ms + enter 150ms) and compares the rendered text with the actual state, stopping at the first desync. The three framer-motion versions are installed side-by-side via pnpm aliases and can be switched with
?v=12.26.2/?v=12.43.0/?v=13.1.1.4. Steps to reproduce
round=1 interval=80ms: rendered="104" state="120" << BUG: stuck forever5. Expected behavior
After updates stop, the last committed child (the latest
key) should always end up rendered, even if intermediate children were skipped while waiting for exit animations.6. Video or screenshots
Results from the automated sweep (2026-09-02, Chromium 148 / React 18.3.1 / Vite 8.2.2, macOS):
round=1 interval=16ms: rendered="6" state="20" << BUG: stuck foreverround=1 interval=80ms: rendered="104" state="120" << BUG: stuck foreverround=1 interval=80ms: rendered="107" state="120" << BUG: stuck foreverBecause the race is timing-dependent, the interval that triggers it varies between runs and machines; the sweep usually catches it within the first round.
7. Environment details
motionwrapper involved)