diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3fd5279ed7..64665f2eab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ Motion adheres to [Semantic Versioning](http://semver.org/).
Undocumented APIs should be considered internal and may change without warning.
+## [12.33.1] 2026-02-06
+
+### Fixed
+
+- `AnimatePresence`: Ensure exiting nodes are correctly removed when rapidly switching children.
+
## [12.33.0] 2026-02-05
### Added
diff --git a/README.md b/README.md
index 93444459d1..e3c68a2526 100644
--- a/README.md
+++ b/README.md
@@ -128,11 +128,12 @@ Motion drives the animations on the Cursor homepage, and is working with Cursor
### Platinum
-
+
+
### Gold
-
+
### Silver
diff --git a/dev/html/package.json b/dev/html/package.json
index 344e112257..3d815866f6 100644
--- a/dev/html/package.json
+++ b/dev/html/package.json
@@ -1,7 +1,7 @@
{
"name": "html-env",
"private": true,
- "version": "12.33.0",
+ "version": "12.33.1",
"type": "module",
"scripts": {
"dev": "vite",
@@ -10,9 +10,9 @@
"preview": "vite preview"
},
"dependencies": {
- "framer-motion": "^12.33.0",
- "motion": "^12.33.0",
- "motion-dom": "^12.33.0",
+ "framer-motion": "^12.33.1",
+ "motion": "^12.33.1",
+ "motion-dom": "^12.33.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
diff --git a/dev/next/package.json b/dev/next/package.json
index 438de224b8..61da8f8a81 100644
--- a/dev/next/package.json
+++ b/dev/next/package.json
@@ -1,7 +1,7 @@
{
"name": "next-env",
"private": true,
- "version": "12.33.0",
+ "version": "12.33.1",
"type": "module",
"scripts": {
"dev": "next dev",
@@ -10,7 +10,7 @@
"build": "next build"
},
"dependencies": {
- "motion": "^12.33.0",
+ "motion": "^12.33.1",
"next": "15.4.10",
"react": "19.0.0",
"react-dom": "19.0.0"
diff --git a/dev/react-19/package.json b/dev/react-19/package.json
index 54b73dc456..996031615a 100644
--- a/dev/react-19/package.json
+++ b/dev/react-19/package.json
@@ -1,7 +1,7 @@
{
"name": "react-19-env",
"private": true,
- "version": "12.33.0",
+ "version": "12.33.1",
"type": "module",
"scripts": {
"dev": "vite",
@@ -11,7 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
- "motion": "^12.33.0",
+ "motion": "^12.33.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
diff --git a/dev/react/package.json b/dev/react/package.json
index 39f30c7557..a8baa0f678 100644
--- a/dev/react/package.json
+++ b/dev/react/package.json
@@ -1,7 +1,7 @@
{
"name": "react-env",
"private": true,
- "version": "12.33.0",
+ "version": "12.33.1",
"type": "module",
"scripts": {
"dev": "yarn vite",
@@ -11,7 +11,7 @@
"preview": "yarn vite preview"
},
"dependencies": {
- "framer-motion": "^12.33.0",
+ "framer-motion": "^12.33.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
diff --git a/lerna.json b/lerna.json
index cccd6e05fd..d1f0925b41 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "12.33.0",
+ "version": "12.33.1",
"packages": [
"packages/*",
"dev/*"
diff --git a/packages/framer-motion/README.md b/packages/framer-motion/README.md
index e9e29ab30d..da283907b0 100644
--- a/packages/framer-motion/README.md
+++ b/packages/framer-motion/README.md
@@ -124,11 +124,12 @@ Motion drives the animations on the Cursor homepage, and is working with Cursor
### Platinum
-
+
+
### Gold
-
+
### Silver
diff --git a/packages/framer-motion/package.json b/packages/framer-motion/package.json
index 59ac460c5d..6da9f86767 100644
--- a/packages/framer-motion/package.json
+++ b/packages/framer-motion/package.json
@@ -1,6 +1,6 @@
{
"name": "framer-motion",
- "version": "12.33.0",
+ "version": "12.33.1",
"description": "A simple and powerful JavaScript animation library",
"main": "dist/cjs/index.js",
"module": "dist/es/index.mjs",
@@ -88,7 +88,7 @@
"measure": "rollup -c ./rollup.size.config.mjs"
},
"dependencies": {
- "motion-dom": "^12.33.0",
+ "motion-dom": "^12.33.1",
"motion-utils": "^12.29.2",
"tslib": "^2.4.0"
},
diff --git a/packages/framer-motion/src/components/AnimatePresence/__tests__/AnimatePresence.test.tsx b/packages/framer-motion/src/components/AnimatePresence/__tests__/AnimatePresence.test.tsx
index 130861ebdf..24143eb031 100644
--- a/packages/framer-motion/src/components/AnimatePresence/__tests__/AnimatePresence.test.tsx
+++ b/packages/framer-motion/src/components/AnimatePresence/__tests__/AnimatePresence.test.tsx
@@ -1245,4 +1245,149 @@ describe("AnimatePresence with custom components", () => {
})
})
})
+
+ test("Removes exiting children during rapid key switches with dynamic custom variants", async () => {
+ const variants: Variants = {
+ enter: (custom: string) => ({
+ ...(custom === "fade"
+ ? { opacity: 0 }
+ : { x: -100 }),
+ transition: { duration: 0.1 },
+ }),
+ center: {
+ opacity: 1,
+ x: 0,
+ transition: { duration: 0.1 },
+ },
+ exit: (custom: string) => ({
+ ...(custom === "fade"
+ ? { opacity: 0 }
+ : { x: 100 }),
+ transition: { duration: 0.1 },
+ }),
+ }
+
+ const items = [
+ { id: "a", transition: "fade" },
+ { id: "b", transition: "slide" },
+ { id: "c", transition: "fade" },
+ { id: "d", transition: "slide" },
+ ]
+
+ const Component = ({ active }: { active: number }) => {
+ const item = items[active]
+ return (
+
+
+
+ )
+ }
+
+ const { container, rerender } = render()
+ rerender()
+
+ // Rapidly switch through all items
+ await act(async () => {
+ rerender()
+ })
+ await act(async () => {
+ rerender()
+ })
+ await act(async () => {
+ rerender()
+ })
+
+ // Wait for all exit animations to complete
+ await new Promise((resolve) => setTimeout(resolve, 500))
+ await act(async () => {
+ await nextFrame()
+ await nextFrame()
+ })
+
+ // Only the last item should remain
+ expect(container.childElementCount).toBe(1)
+ })
+
+ test("Fires onExitComplete during rapid key switches with dynamic custom variants", async () => {
+ const variants: Variants = {
+ enter: (custom: string) => ({
+ ...(custom === "fade"
+ ? { opacity: 0 }
+ : { x: -100 }),
+ transition: { duration: 0.1 },
+ }),
+ center: {
+ opacity: 1,
+ x: 0,
+ transition: { duration: 0.1 },
+ },
+ exit: (custom: string) => ({
+ ...(custom === "fade"
+ ? { opacity: 0 }
+ : { x: 100 }),
+ transition: { duration: 0.1 },
+ }),
+ }
+
+ const items = [
+ { id: "a", transition: "fade" },
+ { id: "b", transition: "slide" },
+ { id: "c", transition: "fade" },
+ { id: "d", transition: "slide" },
+ ]
+
+ let exitCompleteCount = 0
+
+ const Component = ({ active }: { active: number }) => {
+ const item = items[active]
+ return (
+ {
+ exitCompleteCount++
+ }}
+ >
+
+
+ )
+ }
+
+ const { rerender } = render()
+ rerender()
+
+ // Rapidly switch through all items
+ await act(async () => {
+ rerender()
+ })
+ await act(async () => {
+ rerender()
+ })
+ await act(async () => {
+ rerender()
+ })
+
+ // Wait for all exit animations to complete
+ await new Promise((resolve) => setTimeout(resolve, 500))
+ await act(async () => {
+ await nextFrame()
+ await nextFrame()
+ })
+
+ expect(exitCompleteCount).toBeGreaterThan(0)
+ })
})
diff --git a/packages/motion-dom/package.json b/packages/motion-dom/package.json
index 1cf9ede8ba..0bfb737ecf 100644
--- a/packages/motion-dom/package.json
+++ b/packages/motion-dom/package.json
@@ -1,6 +1,6 @@
{
"name": "motion-dom",
- "version": "12.33.0",
+ "version": "12.33.1",
"author": "Matt Perry",
"license": "MIT",
"repository": "https://github.com/motiondivision/motion",
diff --git a/packages/motion-dom/src/render/utils/animation-state.ts b/packages/motion-dom/src/render/utils/animation-state.ts
index b1cc01bc74..ba8a926644 100644
--- a/packages/motion-dom/src/render/utils/animation-state.ts
+++ b/packages/motion-dom/src/render/utils/animation-state.ts
@@ -197,6 +197,23 @@ export function createAnimationState(visualElement: any): AnimationState {
continue
}
+ /**
+ * If exit is already active and wasn't just activated, skip
+ * re-processing to prevent interrupting running exit animations.
+ * Re-resolving exit with a changed custom value can start new
+ * value animations that stop the originals, leaving the exit
+ * animation promise unresolved and the component stuck in the DOM.
+ */
+ if (type === "exit" && typeState.isActive && activeDelta !== true) {
+ if (typeState.prevResolvedValues) {
+ encounteredKeys = {
+ ...encounteredKeys,
+ ...typeState.prevResolvedValues,
+ }
+ }
+ continue
+ }
+
/**
* As we go look through the values defined on this type, if we detect
* a changed value or a value that was removed in a higher priority, we set
diff --git a/packages/motion/README.md b/packages/motion/README.md
index 3a9a0d9d3b..43d1dee2c8 100644
--- a/packages/motion/README.md
+++ b/packages/motion/README.md
@@ -128,11 +128,12 @@ Motion drives the animations on the Cursor homepage, and is working with Cursor
### Platinum
-
+
+
### Gold
-
+
### Silver
diff --git a/packages/motion/package.json b/packages/motion/package.json
index 3db1446f2e..2519b7dd05 100644
--- a/packages/motion/package.json
+++ b/packages/motion/package.json
@@ -1,6 +1,6 @@
{
"name": "motion",
- "version": "12.33.0",
+ "version": "12.33.1",
"description": "An animation library for JavaScript and React.",
"main": "dist/cjs/index.js",
"module": "dist/es/index.mjs",
@@ -76,7 +76,7 @@
"postpublish": "git push --tags"
},
"dependencies": {
- "framer-motion": "^12.33.0",
+ "framer-motion": "^12.33.1",
"tslib": "^2.4.0"
},
"peerDependencies": {
diff --git a/yarn.lock b/yarn.lock
index 60d07fcb72..a86c263107 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7437,14 +7437,14 @@ __metadata:
languageName: node
linkType: hard
-"framer-motion@^12.33.0, framer-motion@workspace:packages/framer-motion":
+"framer-motion@^12.33.1, framer-motion@workspace:packages/framer-motion":
version: 0.0.0-use.local
resolution: "framer-motion@workspace:packages/framer-motion"
dependencies:
"@radix-ui/react-dialog": ^1.1.15
"@thednp/dommatrix": ^2.0.11
"@types/three": 0.137.0
- motion-dom: ^12.33.0
+ motion-dom: ^12.33.1
motion-utils: ^12.29.2
three: 0.137.0
tslib: ^2.4.0
@@ -8209,9 +8209,9 @@ __metadata:
version: 0.0.0-use.local
resolution: "html-env@workspace:dev/html"
dependencies:
- framer-motion: ^12.33.0
- motion: ^12.33.0
- motion-dom: ^12.33.0
+ framer-motion: ^12.33.1
+ motion: ^12.33.1
+ motion-dom: ^12.33.1
react: ^18.3.1
react-dom: ^18.3.1
vite: ^5.2.0
@@ -10953,7 +10953,7 @@ __metadata:
languageName: node
linkType: hard
-"motion-dom@^12.33.0, motion-dom@workspace:packages/motion-dom":
+"motion-dom@^12.33.1, motion-dom@workspace:packages/motion-dom":
version: 0.0.0-use.local
resolution: "motion-dom@workspace:packages/motion-dom"
dependencies:
@@ -11032,11 +11032,11 @@ __metadata:
languageName: unknown
linkType: soft
-"motion@^12.33.0, motion@workspace:packages/motion":
+"motion@^12.33.1, motion@workspace:packages/motion":
version: 0.0.0-use.local
resolution: "motion@workspace:packages/motion"
dependencies:
- framer-motion: ^12.33.0
+ framer-motion: ^12.33.1
tslib: ^2.4.0
peerDependencies:
"@emotion/is-prop-valid": "*"
@@ -11153,7 +11153,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "next-env@workspace:dev/next"
dependencies:
- motion: ^12.33.0
+ motion: ^12.33.1
next: 15.4.10
react: 19.0.0
react-dom: 19.0.0
@@ -12625,7 +12625,7 @@ __metadata:
"@typescript-eslint/parser": ^7.2.0
"@vitejs/plugin-react-swc": ^3.5.0
eslint-plugin-react-refresh: ^0.4.6
- motion: ^12.33.0
+ motion: ^12.33.1
react: ^19.0.0
react-dom: ^19.0.0
vite: ^5.2.0
@@ -12709,7 +12709,7 @@ __metadata:
"@typescript-eslint/parser": ^7.2.0
"@vitejs/plugin-react-swc": ^3.5.0
eslint-plugin-react-refresh: ^0.4.6
- framer-motion: ^12.33.0
+ framer-motion: ^12.33.1
react: ^18.3.1
react-dom: ^18.3.1
vite: ^5.2.0