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.