The View Transitions API animates between two states of a page without you having to keep both on screen yourself. The browser takes a snapshot of the old view, you change the DOM (or the browser loads the next page), it snapshots the new view, and then it animates from one snapshot to the other with CSS. For a multi page site you opt in with one CSS rule, @view-transition { navigation: auto; }; for a single page app you wrap the DOM update in document.startViewTransition().
Cross-document or same-document. If your site is made of real HTML pages, use cross-document transitions: both pages include the opt-in rule and the browser handles the rest on every same origin navigation. If you route on the client (a SPA, or a site that fetches the next page and swaps the content), call document.startViewTransition(update) and do the swap inside update. The animation side is identical in both cases.
What to name. By default the whole page is one group called root, and the browser crossfades it. Giving an element a view-transition-name lifts it into its own group, so a thumbnail can grow into the hero image of the next page while everything else fades. Every name must be unique on the page at the moment of the snapshot, which is the rule most people break first.
How to animate. The snapshots are exposed as pseudo-elements, ::view-transition-old(name) and ::view-transition-new(name), and you animate them with ordinary CSS keyframes. That is where the curtain, the slide or the clip-path wipe lives. GSAP is still useful afterwards, for the intro of the new page once it is in place.
A minimal version
The CSS below gives every navigation a slide up and a clip-path wipe. For a site of plain HTML pages, these lines in a shared stylesheet are the whole implementation.
For a client side router, the same CSS works and the JavaScript only has to perform the swap inside the transition. Without the API the function still swaps the content, just without animation.
async function navigate(url) {
const html = await (await fetch(url)).text();
const next = new DOMParser().parseFromString(html, "text/html");
const swap = () => {
document.querySelector("main").replaceWith(next.querySelector("main"));
document.title = next.title;
};
if (!document.startViewTransition) return swap();
await document.startViewTransition(swap).finished;
}
Wire it to your links with history.pushState, and handle popstate so the back button runs the same function.
Where it gets hard
Browser support. Chromium browsers and Safari 18 support both kinds of transition. Other engines added support later or partially, so check current support before you rely on it. The good news is that the fallback is automatic: where the API is missing, navigation simply happens without the animation.
Duplicate names. If two elements share a view-transition-name when the snapshot is taken, the transition is skipped entirely. Lists of cards are the usual culprit. Assign the name only to the clicked item, just before starting the transition, and remove it afterwards.
Scroll position and focus. In a client router you are responsible for both. Scroll to the top inside the update callback, not after the transition, or the new page animates in at the old scroll position. Move focus to the new main heading so keyboard and screen reader users know the page changed.
Running GSAP intros. Start the intro of the incoming page after transition.finished (or in the pagereveal event for cross-document navigation), otherwise your tweens play underneath a snapshot nobody can see. Kill or revert ScrollTriggers from the old page before the swap, so they do not keep measuring elements that no longer exist.
Reduced motion. Wrap the custom keyframes in @media (prefers-reduced-motion: no-preference). Visitors who prefer less motion then get the default quick crossfade, or none.
Performance
The animation runs on snapshots, which the browser composites as images, so even a full page wipe is cheap. The cost is in what happens before it: fetching the next page and building its DOM. Keep the transition short enough that a slow network does not leave the old page frozen, and prefetch likely next pages on hover so the swap starts with the HTML already in memory.
Variations in the library
Each one has a live demo and a validated AI prompt that rebuilds it in your project.
The live example above, with a free prompt. Links are intercepted, the next page is fetched and swapped inside a view transition: the old page slides up and dims while the new one wipes in from the bottom.
The same slide and clip wipe organised the way a Next.js app routes, with views swapped on the client, so the transition has to wrap the route change instead of a page load.
A slower, more theatrical take: the incoming view opens from a thin horizontal slit, while a dark GSAP overlay and masked heading text finish the reveal.
Not built on the API at all, and that is the point of comparison: a grid of blocks flickers over the page with GSAP, which works in every browser.
Build it with an AI prompt
The snippet above is the bare idea. Page Transitions with the View Transition API is the finished, designed version, and its prompt is free: paste it into Claude Code, Cursor or any AI coding agent and it rebuilds the effect in your project with the exact structure, timing and easing.
Not for a multi page site. Cross-document transitions work with the CSS opt-in alone. You only need document.startViewTransition() when the page itself changes the DOM, as in a single page app.
Can I use GSAP with the View Transitions API?
Yes, side by side. The API animates the snapshots with CSS, and GSAP animates the real elements of the new page once the transition has finished, for example a heading that reveals line by line.
How do I use View Transitions in Next.js?
With the app router you need the navigation to happen inside startViewTransition. Community packages such as next-view-transitions wrap the router for you, and React is adding its own ViewTransition component. The CSS for the animation stays the same.
Does the transition work when the user presses back?
Cross-document transitions run on back and forward navigations too. In a client router, run the same navigation function from your popstate handler.