diff --git a/packages/react-aria-components/test/Dialog.test.js b/packages/react-aria-components/test/Dialog.test.js
index 3c28c938367..a4b730c7194 100644
--- a/packages/react-aria-components/test/Dialog.test.js
+++ b/packages/react-aria-components/test/Dialog.test.js
@@ -525,6 +525,25 @@ describe('Dialog', () => {
expect(overlay).not.toBeInTheDocument();
});
+ it('unmounts when the exit animation is cancelled', async () => {
+ let {getByRole, getByTestId} = render();
+
+ await user.click(getByRole('button'));
+ let overlay = getByTestId('overlay');
+
+ let reject;
+ let finished = new Promise((_, r) => (reject = r));
+ Element.prototype.getAnimations = () => [{playState: 'running', finished}];
+
+ await user.click(getByRole('button', {name: 'Close'}));
+ expect(overlay).toBeInTheDocument();
+ expect(overlay).toHaveAttribute('data-exiting');
+
+ // Cancelling an animation rejects its finished promise.
+ await act(async () => reject(new DOMException('The animation was cancelled', 'AbortError')));
+ expect(overlay).not.toBeInTheDocument();
+ });
+
it('calls the latest onEnter and onExit callbacks', async () => {
let enterPromise = deferred();
let exitPromise = deferred();
diff --git a/packages/react-aria/src/utils/animation.ts b/packages/react-aria/src/utils/animation.ts
index 174c105a0ed..2439ab1d7dc 100644
--- a/packages/react-aria/src/utils/animation.ts
+++ b/packages/react-aria/src/utils/animation.ts
@@ -111,6 +111,7 @@ function useAnimation(
onEnd: () => void
): void {
let isActiveRef = useRef(null);
+ let currentRun = useRef