What Are CSS Page Transitions?
Under the hood it’s a snapshot trick. The browser grabs the old view and the new view, then animates between them with CSS. MDN Web Docs describes the View Transition API as covering DOM changes inside a single-page app and navigation between separate documents in a multi-page app.
That gives you same-document transitions, which run inside one page, and cross-document transitions, which run between two pages of the same site.
It helps to keep the older property in perspective. The transition property moves one element between two style states, like a color change on hover. The View Transition API animates a whole view, or named parts of it, between two DOM states or two documents. And because the old document is gone before the new one paints, the transition property has no way to cover a full page change.
Most demo galleries under this keyword fake the effect. They stack panels, toggle classes and rely on a plain CSS animation to slide one block over another.
Before the API, a single-page app needed a pile of JavaScript just to position the old content, animate it out and remove it afterward, as MDN points out. Cross-document transitions weren’t possible at all.
How Does the View Transition API Animate a Page Change?
Capture, update, capture again. After that the browser plays a CSS animation on a tree of pseudo-elements laid over the page, and the default animation is a cross-fade.
The Snapshot Sequence
Chrome for Developers documents the order in a guide written by Jake Archibald and Bramus Van Damme.
The old state comes first, with snapshots of the root and every element that has a view-transition-name. Your callback then runs while rendering is suppressed. Once it finishes, the browser captures the result and builds the pseudo-element tree, and CSS animations play over the page until the live DOM becomes visible and interactive again.
Only one view transition runs at a time. If a second one starts mid-animation, the first skips to its end.
The Pseudo-Element Tree
The tree sits in an overlay above the page, so styling it never touches your real elements.
| Pseudo-element | What it holds |
|---|
| ::view-transition | The overlay root that fills the viewport |
| ::view-transition-group() | One named transition, animating size and position |
| ::view-transition-image-pair() | Container for the old and new views |
| ::view-transition-old() | Static screenshot of the old view |
| ::view-transition-new() | Live render of the new view |
This split is why video behaves oddly. The old view stays frozen while the new one keeps playing.
By default the old view fades from opacity 1 to 0 while the new view goes from 0 to 1.
Specification Levels
Two drafts from the W3C CSS Working Group define the feature, and MDN links both to the working group’s editor drafts as of its September 28, 2026 update.
Level 1 is the same-document side: document.startViewTransition(), view-transition-name and the pseudo-elements. Cross-document transitions live in Level 2, along with the @view-transition at-rule, view transition types and view-transition-class.
The pageswap and pagereveal events belong to neither CSS module. Chrome for Developers places them in the HTML specification.
How to Build a Same-Document Page Transition
Chrome has supported document.startViewTransition() since version 111, per Chrome for Developers. Wrap your DOM update in it and the browser animates the change with a default cross-fade.
- Check that
document.startViewTransition exists. If it doesn’t, run the DOM update directly and skip the animation. - Pass the function that changes the DOM as the callback. Add or remove elements, switch classes or change styles inside it.
- Keep the callback short, since the page freezes while it runs. Do network fetches before the call. If you wait for fonts or images, use an aggressive timeout (Chrome’s guide caps the font wait at 100 ms).
- Style
::view-transition-old(root) and ::view-transition-new(root) with animation-duration or your own keyframes. - Await
updateCallbackDone, not ready, when app logic depends on the result. A skipped transition then can’t break the app.
function spaNavigate(data) {
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
document.startViewTransition(() => updateTheDOMSomehow(data));
}
Two failure modes are documented. Duplicate view-transition-name values on rendered elements skip the transition, and a callback that returns a rejected promise abandons it.
Chrome’s case study on redBus says view transitions made page-to-page navigation smoother and raised conversion rate in one of its user journeys.
Cross-Document Page Transitions With CSS Only
Chrome has supported this since version 126 (Chrome for Developers). Add a @view-transition rule with navigation: auto to both pages and a same-origin link click animates with no JavaScript.
@view-transition {
navigation: auto;
}
Both documents need to share an origin, and both need the rule. No API call is involved, since the link click itself starts the transition.
The @view-transition At-Rule
A navigation that takes more than four seconds skips the transition in Chrome and throws a TimeoutError, according to Chrome for Developers.
Transitions run on traverse navigations, plus push or replace navigations that weren’t started through browser UI. Typing in the address bar, clicking a bookmark or reloading skips them.
Early experiments used a meta tag to opt in. Chrome calls that a temporary measure, and the CSS at-rule replaced it.
Customizing With pageswap and pagereveal
Both events fire for every same-origin cross-document navigation, whether or not a transition runs. The viewTransition property on the event exists only when one is about to play.
pageswap fires on the outgoing page before its last frame renders, so last-minute changes go there, before the old snapshots are taken. On the incoming side, pagereveal fires before the first render. That’s where you set names or transition types ahead of the new snapshots.
People use these events to name only the clicked list item just in time, or to pass data between pages through sessionStorage. skipTransition() is there for the cases where a transition makes no sense.
Register the pagereveal listener in a classic script in the head. Scripts loaded as modules, or with async or defer, can miss the first render unless marked blocking=render.
Google used cross-document view transitions to connect standard search with AI Mode, according to its August 2025 case study on Chrome for Developers.
Customizing the Animation: Names, Classes, Duration and Easing
An element with its own view-transition-name animates on its own. view-transition-class groups similar ones, and the default fade can be swapped for keyframes, duration and easing on the old and new pseudo-elements.
Shared Element Transitions
A shared element transition links two different elements through one name. A product thumbnail grows into the hero image on the detail page because both carry the same name during the transition.
Names have to be unique within a rendered state, or the transition is skipped. The two elements don’t need to be the same DOM node. In a single-page app, set the name on the thumbnail just before the call and clear it inside the callback. If the aspect ratios differ, use object-fit: contain on the old view and cover on the new one.
Naming the header works the same way. It stays in place and cross-fades while the rest of the page slides, as in Chrome’s fixed-header demo.
Duration, Easing and Slide Effects
Chrome’s shared axis example pairs a 90 ms fade-out with a 300 ms slide, each with its own cubic-bezier easing (Chrome for Developers).
Both are defined with CSS keyframes and attached through the animation shorthand on the old and new root pseudo-elements.
view-transition-class applies one style rule to many named elements, so a card grid needs one selector instead of one per card. For direction, view transition types such as :active-view-transition-type(forwards) and backwards pick the slide. And when CSS can’t do something, like a circle that grows from the click position, the Web Animations API covers it.
Policybazaar raised the default animation duration to 0.4 s on one call-to-action transition, per Chrome’s e-commerce case study. Short durations suit page changes better than long, showy ones. Past half a second it starts to feel like waiting.
Which Browsers Support CSS Page Transitions?
Same-document transitions work in all three major engines (Chromium, WebKit and Gecko). Cross-document transitions are the uneven part, and MDN still rates the @view-transition at-rule as limited availability, not Baseline, in its April 20, 2026 revision.
Support by Browser and Version
| Browser | Same-document | Cross-document |
|---|
| Chrome and Edge | Supported | Supported |
| Safari | 18.0 | 18.2 (December 11, 2024) |
| Firefox | 144 (October 14, 2025) | Not supported |
Firefox 144 moved same-document transitions into Baseline as newly available in October 2025, according to MDN. Mozilla’s release notes for that version list the API and its CSS pieces for single-page apps, and nothing for cross-document navigation.
One cell in that table is contested. Some secondary guides claim Firefox supports cross-document transitions from version 132 or 144, while MDN and Mozilla’s own notes show no such entry. Treat those claims as unverified until MDN’s compatibility table changes. View transition types came later, in Firefox 147 (January 13, 2026), which made them Baseline as well.
A second conflict sits between two primary sources. Chrome’s cross-document guide, last updated April 14, 2024, lists Safari as unsupported for render blocking, while WebKit’s Safari 18.2 post says the release added blocking=render and link rel=expect.
The WebKit post is newer, so Safari counts as supported. Either way, check the update date on any support box before trusting it.
Detecting Support and Falling Back
A browser without support skips the animation and loads the page as usual.
With same-document transitions, the DOM update still runs and only the animation is missing. For cross-document, you get a normal page load, because the browser ignores an @view-transition rule it doesn’t recognize, like any unknown at-rule. Custom pseudo-element CSS never matches, so it does no harm.
That makes the animation an extra layer on top of working navigation. Test it as part of your cross-browser compatibility pass, with the animation absent, and confirm every link and DOM update still works.
Where CSS Page Transitions Hurt Performance and Accessibility
A transition hurts when the page stays frozen too long, and it hurts when motion plays for visitors who asked for less of it. Blocked rendering can also delay the first paint.
Performance Costs
The update step is the first cost. While your callback runs, the page is frozen, and web.dev rates an INP of 200 milliseconds or less as good and anything above 500 as poor, measured at the 75th percentile.
INP runs until the next painted frame after a click, tap or key press. A callback that holds the page frozen is therefore time the metric can count.
Then there’s layout. The default group animation changes width and height, which Chrome’s guide (updated September 25, 2024) says triggers layout on every frame. At that update, the planned optimization hadn’t shipped.
Render blocking is the last one. Chrome advises against blocking=”render” by default unless you can measure the Core Web Vitals impact. MDN adds a useful lever here: a media attribute on link rel=expect lets narrow screens wait for less content than wide ones.
Prerendering With the Speculation Rules API
Prerendering gives a cross-document transition a loaded page to animate into.
Chrome’s prerender guide (updated January 23, 2026) reports near-zero LCP, lower CLS and better INP when a prerendered page activates. Prerenders stay same-origin by default, which matches the transition requirement, and a prerendered document fires pagereveal on activation (MDN).
The costs are real, though. Chrome caps immediate rules at 10 prerenders, and the browser treats each rule as a hint it can ignore. Single-page apps are out too, since in-app route changes can’t be prerendered.
Reduced Motion and WCAG
W3C classes Success Criterion 2.3.3, Animation from Interactions, at Level AAA. Sites aiming for AA are not required to meet it, but the criterion describes the right behavior for web accessibility.
Its definition of motion animation leaves out color and opacity changes that do not alter perceived size, shape or position. So the default cross-fade doesn’t count, but slides and growing images do.
The preference reaches CSS through a media query, and Chrome’s guide shows how to switch transitions off with it.
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Chrome also notes that a reduced-motion preference does not always mean no motion. A subtler animation that still shows how elements relate is a valid choice.
Frameworks handle this differently, and the differences matter. Astro’s client router disables all view transition animations when the preference is detected. React doesn’t disable animations automatically, and its docs tell you to add the media query yourself. With Nuxt, experimental.viewTransition set to true skips transitions for reduced-motion users, while the value always leaves that decision to you.
Why a Page Transition Does Not Run
Most skipped transitions fail quietly. Nothing shows up on screen, and a short list of causes covers nearly all of them.
A hidden tab is the simplest. If the page visibility state is hidden when startViewTransition is called, the transition is skipped entirely (MDN).
Cross-document transitions need the navigation to be same-origin with no cross-origin redirect in the chain. If there is one, the activation property returns null.
Names left behind cause stranger bugs. A view-transition-name set in pageswap and never cleared returns from the back/forward cache, and the next pagereveal then creates duplicates. MDN’s fix is to reset names after the snapshots are taken.
Pseudo-element CSS is needed in the destination document only. A one-directional animation therefore needs the rules in both pages to cover the return trip.
Some browsers draw their own transition on swipe-back, and Chrome’s guide says to check hasUAVisualTransition before starting another. An operating system reduced-motion setting can also switch the animation off while everything else works.
Chrome DevTools covers most debugging. The Animations panel pauses the next animation so you can scrub through it, and the transition pseudo-elements appear in the Elements panel.
React has its own pitfalls, per its ViewTransition reference. A DOM node above the component stops enter and exit animations. Updates outside a Transition, Suspense or useDeferredValue never activate it. And animations from the legacy popstate event are skipped, so the back button stays still until the router uses the Navigation API.
CSS Page Transitions vs JavaScript Libraries and Framework Routers
Start with the native feature. Add a library only when you need an element to persist across pages or an animation in browsers that lack support.
| Approach | JavaScript needed | Animates without native support | Best fit |
|---|
| Native cross-document | No | No, plain load | Same-origin multi-page sites |
| Native same-document | One call | No, update only | Single-page apps |
| Barba.js or Swup | Yes | Yes, library animates | Sites that need guaranteed motion |
| Framework routers | Yes | Varies by framework | Apps already on that framework |
Native View Transitions
Native transitions add no library weight and no script re-initialization, and the fallback is simply a normal page load. The downsides are uneven cross-document support and no way to keep a playing video or audio element alive across two separate documents.
Barba.js, Swup and GSAP
Barba.js uses PJAX. It blocks the normal link behavior, updates the URL with the history API, fetches the next page in the background, injects the new container, then removes the old one, according to its documentation.
Swup follows a similar idea. It intercepts clicks, loads the page in the background and replaces only the content containers. Before swapping, it waits for CSS animations, JavaScript animations and native View Transitions.
GSAP is an animation engine, not a router. Webflow made it free, including the former Club plugins, with a license that covers commercial use.
What you get from these tools is animation in any browser, plus prefetch and caching. The price is extra JavaScript and scripts that must re-run after each swap, and you now own the history handling.
Framework Routers
Astro’s ClientRouter component is opt-in, offers animate, swap and none fallbacks, and keeps elements such as a video playing through transition:persist. Astro’s own docs add that the router will become less necessary as browser APIs mature.
In SvelteKit, the onNavigate callback can return a promise, which is where document.startViewTransition fits. The docs warn against slow promises, because navigation then appears stalled.
Nuxt treats viewTransition as an experimental option, and the docs flag a known issue with data fetching in page setup, since transitions freeze DOM updates.
React’s ViewTransition component calls startViewTransition itself and interrupts any other view transition running on the page, so its docs say not to call it yourself.
My rule of thumb is to start with the native rule, check your analytics for the browsers you must cover, and reach for a library only when a persistent element or guaranteed motion justifies the added JavaScript.
CSS Page Transitions FAQ
Do page transitions affect SEO?
Neither MDN nor Chrome documents any ranking effect. Cross-document transitions leave each page a normal document with its own URL, and Astro’s docs say they do not alter core multi-page behavior.
The indirect risk is speed, because a slow update step hurts responsiveness metrics.
Can a page transition run between two different domains?
No. Chrome limits cross-document transitions to same-origin navigations, so a move from developer.chrome.com to www.chrome.com fails because the hostnames differ.
MDN notes the restriction could be relaxed in future implementations.
Does the back button play the transition in reverse?
Not automatically. A slide that moves right to left on every navigation feels wrong when the visitor goes back, as Chrome’s guide points out.
Use view transition types to assign a backwards animation, and set a type for each direction.
Do Webflow and WordPress sites support page transitions?
Yes, because the cross-document rule needs only CSS and no JavaScript. Webflow lists a CSS-only cloneable demo that animates where cross-document transitions are supported and loads normally elsewhere.
WordPress serves full pages from one origin, so a rule in the theme stylesheet reaches both documents.
Rolling Out CSS Page Transitions Safely
Ship the default cross-fade first and measure it in the field before adding any custom motion. That order keeps failures cheap, since the default animation adds the least work to each page load.
Opt both pages into the cross-fade, then compare real-user INP and LCP before and after. If the numbers hold, add shared elements, then types, then render blocking.
Render blocking goes last because Chrome advises measuring its Core Web Vitals cost and Safari supports it as well, so one field measurement per browser decides whether it stays.
This advice changes the day Mozilla lists @view-transition in a Firefox release note. The position was verified against MDN and Mozilla’s notes on October 27, 2026.
Draft the keyframes for the custom motion step with a CSS animation generator before wiring them to the root pseudo-elements.