Web development
View transitions without a SPA: what works between two HTML pages
Cross-document view transitions animate the move between two HTML pages with one CSS rule, no SPA. Where they work and where the page stays instant.
Key points
- One rule, @view-transition { navigation: auto }, turns on transitions between pages of the same site.
- Shared elements need the same view-transition-name on both pages, and that name must be unique per page.
- Where the API is missing the navigation stays instant: it is an improvement, not a dependency.
Short answer: cross-document view transitions animate the move between two HTML pages with one CSS rule, no SPA. Chrome 126+ and Safari 18.2+ support them; elsewhere the navigation stays instant, with no errors.
The minimum that works
@view-transition {
navigation: auto;
}
::view-transition-old(root) {
animation: 180ms ease-out both fade-out;
}
::view-transition-new(root) {
animation: 180ms ease-in both fade-in;
}
@media (prefers-reduced-motion: reduce) {
::view-transition-old(root),
::view-transition-new(root) { animation: none; }
}
That is enough for a cross-fade on every navigation between pages of the same origin. The two animations, fade-out and fade-in, must be defined with @keyframes in your own sheet: the browser does not ship them.
Shared elements
.card__media { view-transition-name: cover; }
.card__title { view-transition-name: title; }
/* la stessa coppia di nomi nella pagina di destinazione */
.article__media { view-transition-name: cover; }
.article__title { view-transition-name: title; }
The name must be unique on the page. When two elements share one, the browser drops the transition for that element: it is the most common mistake, and it logs nothing to the console.
| Case | What it needs | Note |
|---|---|---|
| Page change | @view-transition { navigation: auto } | same origin only |
| Shared element | a unique view-transition-name | one per page, not two |
| Single-page app | document.startViewTransition() | needs a callback that updates the DOM |
The cross-document path needs no JavaScript: the transition also fires on the back button and on a link opened in a new tab. Support is not universal, though, so treat the rule as progressive enhancement.
The limitation
Three real limits. First, the transition does not survive a theme change between the two pages, because the cross-fade shows two different layouts. Second, Firefox does not support it yet, so check caniuse on the day you publish. Third, the browser takes a snapshot of the page, so on very large documents you feel the cost in the frame.
How to check it
- Emulate
prefers-reduced-motionin DevTools: no animation may remain. - Navigate by keyboard: the transition must not swallow focus or the tab order.
- Look for a duplicate name: with two the transition disappears in silence.
- Try the back button: the transition must fire there too, not only on links.
Where to continue
In the catalogue: blog block, components and blocks. The guides: view transitions in Astro and CSS animations in Tailwind; the comparison: Tailwind against CSS Modules.
Frequently asked questions
Do I need a SPA to use them?
No. The cross-document case animates navigation between two real documents. A SPA only matters when you want to control when the DOM updates.
Do they work on Safari and Firefox?
Safari supports them from 18.2, Chrome from 126. Firefox is still behind: with no support the navigation stays instant and nothing breaks.
Why does the transition not fire?
Almost always a duplicate name, or the <code>@view-transition</code> rule missing from one of the two pages. Both need it.
How do I turn them off for people who want less motion?
With a <code>prefers-reduced-motion: reduce</code> media query that zeroes the pseudo-element animations. The browser respects the system preference.
Do they hurt Core Web Vitals?
The cost is one snapshot per navigation. On ordinary pages it stays under a frame; on very long documents, measure INP with the Performance panel.