Exit Animation for Intercepting Route Modal (Next.js)

This feature isn’t supported in Next.js, but we can use the modal state to trigger route changes after the animation ends.

- Updated on

Update

It looks like React just merged support for the View Transitions API, which provides an easy way to handle transitions for your application since it’s native to the browser. You can check out the PR here, and find more info about the API here.

This solves the issue of exit animations; however, it will likely take some time before it’s released in the stable version of Next.js. In the meantime, you can still use the solution below.

The issue

Since Next.js doesn’t support exit animations yet (Relevant issue), we need to manually create the animation event.

For this example I’m using the Sheet component from shadcn/ui, but you can use the Dialog from Radix directly with your own style.

Initial modal component

This is our initial component using shadcn/ui Drawer component, based on the Next.js docs.

// app/components/modal.tsx

"use client";

import { useRouter } from "next/navigation";
import { Sheet, SheetContent } from "@/components/ui/sheet";

export default function Modal({ children }: { children: React.ReactNode }) {
    const router = useRouter();

    function handleClose() {
        router.back();
    }

    return (
        <Sheet open onOpenChange={handleClose}>
            <SheetContent
                side="bottom"
                className="h-full overflow-auto rounded-t-3xl px-0 py-16 md:h-[96%]"
            >
                {children}
            </SheetContent>
        </Sheet>
    );
}

Where we just call the router.back() to close the modal by switching the URL.

Adding a closing animation

To achieve this, we need to control the open state to perform the router.back() after the closing animation ends, using onAnimationEndCapture.

// app/components/modal.tsx

"use client";

import { useRouter } from "next/navigation";
import { Sheet, SheetContent } from "@/components/ui/sheet";
import { useState } from "react"; 

export default function Modal({ children }: { children: React.ReactNode }) {
    const [open, setOpen] = useState(true); 
    const router = useRouter();

    function handleClose() {
        router.back(); 
        setOpen(false); 
    }

    function handleAnimationEnd() {

        // when the modal animation ends: if it's closed, navigate back
        if (!open) {

            router.back(); 
        } 
    } 

    return (
        <Sheet open={open} onOpenChange={handleClose}>
            <SheetContent
                onAnimationEndCapture={handleAnimationEnd} 
                side="bottom"
                className="h-full overflow-auto rounded-t-3xl px-0 py-16 md:h-[96%]"
            >
                {children}
            </SheetContent>
        </Sheet>
    );
}

Result

Now when pressing the close button or the ESC key, the modal will exit with an animation then go back to the previous URL.

We still can’t animate when the React component is unmounted (eg: when using the browser’s back button), for that we’ll have to wait for native framework support.

For now this cover the rest of usecases.