From 9474879a6c0b27b0278a52569e7e7648671116dc Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Fri, 6 Feb 2026 08:55:20 +0100 Subject: [PATCH 1/7] Revise Platinum and Gold sponsor sections Updated Platinum and Gold sections with new links and images. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 93444459d1..e40aa293d9 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 -Linear Figma Sanity Sanity +Linear Figma Sanity Sanity Clerk + ### Gold -Liveblocks Luma Notion LottieFiles +Liveblocks Luma LottieFiles ### Silver From 332646db7bfcfe0a1b4884f82d51606cd11cb294 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Fri, 6 Feb 2026 08:55:51 +0100 Subject: [PATCH 2/7] Fix image source for Clerk in Platinum section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e40aa293d9..e3c68a2526 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Motion drives the animations on the Cursor homepage, and is working with Cursor ### Platinum -Linear Figma Sanity Sanity Clerk +Linear Figma Sanity Sanity Clerk ### Gold From 097ef8b377142372b870165018dc4f1652e609bd Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Fri, 6 Feb 2026 08:56:21 +0100 Subject: [PATCH 3/7] Add Clerk sponsor image to Platinum section Added a new sponsor image for Clerk in the Platinum section. --- packages/framer-motion/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 -Linear Figma Sanity Sanity +Linear Figma Sanity Sanity Clerk + ### Gold -Liveblocks Luma Notion LottieFiles +Liveblocks Luma LottieFiles ### Silver From 7086ea1e78601d56b6ae6974459570a065a14a4a Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Fri, 6 Feb 2026 08:56:42 +0100 Subject: [PATCH 4/7] Duplicate Platinum and Gold section links --- packages/motion/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 -Linear Figma Sanity Sanity +Linear Figma Sanity Sanity Clerk + ### Gold -Liveblocks Luma Notion LottieFiles +Liveblocks Luma LottieFiles ### Silver From 8798d7017a56e1bece6b06a146eeba0740b299c2 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Fri, 6 Feb 2026 09:09:53 +0100 Subject: [PATCH 5/7] Fix AnimatePresence keeping exiting children in DOM during rapid updates with dynamic variants When rapidly switching children inside AnimatePresence with dynamic custom variants (where animated properties differ between items), exiting children could get stuck in the DOM. This happened because animateChanges() re-resolved exit variants on every re-render with the latest custom value, starting new value animations that interrupted the originals. Since JSAnimation.stop() doesn't resolve its finished promise, the exit completion chain would hang forever, preventing onExitComplete from firing and leaving elements in the DOM. Fix: skip re-processing the exit animation type in animateChanges() when it's already active and wasn't just explicitly toggled, preserving the running exit animation's promise chain. Fixes motiondivision/motion#3541 Co-Authored-By: Claude Opus 4.6 --- .../__tests__/AnimatePresence.test.tsx | 145 ++++++++++++++++++ .../src/render/utils/animation-state.ts | 17 ++ 2 files changed, 162 insertions(+) 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/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 From 4ffff547843080ecfcac5124d69307fbc1ec7f33 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Fri, 6 Feb 2026 10:43:15 +0100 Subject: [PATCH 6/7] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) 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 From 49b37649a85f2af2ec3dbfb3cec89e3861ae9c35 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Fri, 6 Feb 2026 10:47:32 +0100 Subject: [PATCH 7/7] v12.33.1 --- dev/html/package.json | 8 ++++---- dev/next/package.json | 4 ++-- dev/react-19/package.json | 4 ++-- dev/react/package.json | 4 ++-- lerna.json | 2 +- packages/framer-motion/package.json | 4 ++-- packages/motion-dom/package.json | 2 +- packages/motion/package.json | 4 ++-- yarn.lock | 22 +++++++++++----------- 9 files changed, 27 insertions(+), 27 deletions(-) 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/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/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/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