View Transitions
Animated transitions between states and between pages, with very little code.
The browser captures the before and after and animates the difference. You decide which elements pair up (view-transition-name) and how they move, with CSS pseudo-elements. Within a page, wrap the change in document.startViewTransition().
Within a page they have been Baseline since October 2025 (Firefox 144). Across pages of the same site, @view-transition { navigation: auto; } is enough in Chrome, Edge and Safari; in Firefox it is still in development. This site uses it between entries.
What it is for
- Lists that reorder or filter with continuity
- From list to detail without losing your place
- Multi-page sites that feel like an app
Demo
Every change runs inside document.startViewTransition(): the browser animates the difference. Click a swatch to open it. And across pages: go to the previous or next entry and watch the title.
Swatch
Browser support
Baseline · newly available · 14 Oct 2025| Feature | Desktop | Mobile | |||||
|---|---|---|---|---|---|---|---|
| Chrome | Edge | Firefox | Safari | Chrome Android | Firefox Android | Safari iOS | |
| Same document | 111Chrome: 111 | 111Edge: 111 | 144Firefox: 144 | 18Safari: 18 | 111Chrome: 111 | 144Firefox: 144 | 18Safari: 18 |
| Across pages (@view-transition) | 126Chrome: 126 | 126Edge: 126 | —1Firefox: No | 18.2Safari: 18.2 | 126Chrome: 126 | —Firefox: No | 18.2Safari: 18.2 |
| Element-scoped | 147Chrome: 147 | 147Edge: 147 | —Firefox: No | —Safari: No | 147Chrome: 147 | —Firefox: No | —Safari: No |
- 1In development; part of Interop 2026.
Compatibility generated on every deploy from MDN browser-compat-data 8.1.3 (24 Sep 2026) · web-features 3.40.0 · checked by hand: 1 Oct 2026
Minimal code
<!doctype html>
<meta charset="utf-8">
<title>View Transitions · ejemplo mínimo</title>
<style>
/* Entre páginas del mismo origen: basta con esta regla en las dos páginas */
@view-transition { navigation: auto; }
ul { display: flex; flex-wrap: wrap; gap: 8px; list-style: none; padding: 0; }
li { width: 80px; height: 80px; display: grid; place-items: center; background: #c0643a; color: #fff; font: 600 24px system-ui; }
/* Cada elemento anima por su cuenta; la clase permite darles a todos la misma curva */
li { view-transition-name: match-element; view-transition-class: card; }
::view-transition-group(.card) { animation-duration: 0.4s; }
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none; }
}
</style>
<p><button id="shuffle">Barajar</button> · <a href="pagina-2.html">Ir a la página 2 (transición entre documentos)</a></p>
<ul id="list"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul>
<script>
const list = document.querySelector('#list');
const shuffle = () => [...list.children].sort(() => Math.random() - 0.5).forEach((li) => list.append(li));
document.querySelector('#shuffle').onclick = () => {
// Sin soporte, el cambio se hace igual, sin animación
if (!document.startViewTransition) return shuffle();
document.startViewTransition(shuffle);
};
</script><!doctype html>
<meta charset="utf-8">
<title>View Transitions · ejemplo mínimo</title>
<style>
/* Entre páginas del mismo origen: basta con esta regla en las dos páginas */
@view-transition { navigation: auto; }
ul { display: flex; flex-wrap: wrap; gap: 8px; list-style: none; padding: 0; }
li { width: 80px; height: 80px; display: grid; place-items: center; background: #c0643a; color: #fff; font: 600 24px system-ui; }
/* Cada elemento anima por su cuenta; la clase permite darles a todos la misma curva */
li { view-transition-name: match-element; view-transition-class: card; }
::view-transition-group(.card) { animation-duration: 0.4s; }
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none; }
}
</style>
<p><button id="shuffle">Barajar</button> · <a href="pagina-2.html">Ir a la página 2 (transición entre documentos)</a></p>
<ul id="list"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul>
<script>
const list = document.querySelector('#list');
const shuffle = () => [...list.children].sort(() => Math.random() - 0.5).forEach((li) => list.append(li));
document.querySelector('#shuffle').onclick = () => {
// Sin soporte, el cambio se hace igual, sin animación
if (!document.startViewTransition) return shuffle();
document.startViewTransition(shuffle);
};
</script><!doctype html>
<meta charset="utf-8">
<title>View Transitions · página 2</title>
<style>
@view-transition { navigation: auto; }
h1 { font: 600 32px system-ui; }
</style>
<h1>Página 2</h1>
<p>Esta navegación ha usado una transición entre documentos (Chrome 126+, Safari 18.2+).</p>
<p><a href="index.html">Volver</a></p><!doctype html>
<meta charset="utf-8">
<title>View Transitions · página 2</title>
<style>
@view-transition { navigation: auto; }
h1 { font: 600 32px system-ui; }
</style>
<h1>Página 2</h1>
<p>Esta navegación ha usado una transición entre documentos (Chrome 126+, Safari 18.2+).</p>
<p><a href="index.html">Volver</a></p>What’s new
Keep in mind
- Across pages, both must opt in and be same-origin; a cross-origin redirect cancels it.
- match-element pairs by identity and only works within one document; across pages use unique names.
- Respect prefers-reduced-motion and check that startViewTransition exists before calling it.
Further reading
Sources
- Chrome · Element-scoped view transitions27 Mar 2026
- Interop 202612 Feb 2026
- Bugzilla 1860854 (Firefox, entre documentos)accessed 1 Oct 2026
- web-features · view-transitions30 Sep 2026