View Transitions API
Guia para implementar view transitions en SPAs y MPAs, con soporte para React y Next.js.
Modelo Mental
La View Transitions API automatiza el patron FLIP (First, Last, Invert, Play) a nivel del browser. El browser captura screenshots del estado actual, vos actualizas el DOM, el browser captura el nuevo estado y anima entre ambos usando CSS Animations sobre pseudo-elementos generados.
Hay dos variantes que comparten los mismos building blocks:
| Variante | Trigger | Caso de uso |
|----------|---------|-------------|
| Same-document (SPA) | document.startViewTransition(callback) | Cambios de estado en SPAs |
| Cross-document (MPA) | Navegacion + @view-transition CSS at-rule | Navegacion entre paginas HTML |
Browser Support
- Same-document: Chrome 111+, Edge 111+, Safari 18+, Firefox 133+ — Baseline Newly Available
- Cross-document: Chrome 126+, Edge 126+, Safari 18.2+ — Firefox: en desarrollo
- view-transition-class: Chrome 125+, Safari 18.2+, Firefox 144+
- match-element: Chrome 137+, Safari 18.2+, Firefox 144+
Cross-document transitions es foco de Interop 2026. Siempre implementar con fallback graceful. Verificar soporte actual en caniuse.com.
Implementacion Rapida
Same-Document (SPA)
function navigateWithTransition(updateFn: () => void) {
// SIEMPRE feature detection
if (!document.startViewTransition) {
updateFn();
return;
}
document.startViewTransition(() => updateFn());
}
Cross-Document (MPA)
Agregar en el CSS global (ambas paginas, origen y destino):
/* Opt-in — reemplaza el meta tag obsoleto */
@view-transition {
navigation: auto;
}
Nombrar Elementos para Transicion
/* Mismo view-transition-name en ambos estados = shared element transition */
.thumbnail { view-transition-name: product-hero; }
.hero-image { view-transition-name: product-hero; }
Reglas Criticas
Seguir siempre estas reglas al implementar view transitions:
-
Feature detection obligatorio — Siempre chequear
document.startViewTransitionantes de usarlo. Sin esto, browsers sin soporte rompen. -
view-transition-name unico por snapshot — Dos elementos visibles con el mismo name abortan la transicion silenciosamente. Es el bug #1 mas comun.
-
prefers-reduced-motion — Siempre respetar. Agregar este CSS en todo proyecto:
@media (prefers-reduced-motion: reduce) { ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; } } -
Duracion < 500ms — Sweet spot: 200-400ms. Transiciones largas mantienen snapshots en GPU memory.
-
Limitar elementos nombrados — Cada name crea un par old+new de snapshots rasterizados. 50 elementos = 100 imagenes en memoria. Ser selectivo.
-
Callback liviano — El browser espera que el callback de
startViewTransition()termine antes de capturar el nuevo estado. No meter heavy computation ahi. -
animation-fill-mode: forwards — Aplicar a animaciones custom para evitar flicker al terminar.
-
No usar el meta tag obsoleto —
<meta name="view-transition">es obsoleto. Usar@view-transition { navigation: auto; }. -
MPA: opt-in en AMBAS paginas — La at-rule debe estar en el CSS de la pagina origen Y la destino.
-
React: no usar flushSync — Un
flushSyncdurante una view transition fuerza a React a aplicar updates sincronicamente, lo cual skipea el flujo destartViewTransition.
Pseudo-elementos
Durante la transicion, el browser genera este arbol que se puede estilizar con CSS:
::view-transition
::view-transition-group(name)
::view-transition-image-pair(name)
::view-transition-old(name) → screenshot estado anterior
::view-transition-new(name) → vista live del nuevo estado
Customizar:
/* Duracion global */
::view-transition-group(*) { animation-duration: 0.4s; }
/* Desactivar crossfade root, solo animar named elements */
::view-transition-group(root) { animation: none; }
ViewTransition Object (SPA)
startViewTransition() retorna un objeto con tres promises:
| Promise | Se resuelve cuando... |
|---------|----------------------|
| updateCallbackDone | El callback del DOM termino |
| ready | Los pseudo-elementos estan creados, animacion por arrancar |
| finished | La animacion termino, nueva vista visible e interactiva |
Usar transition.ready para hijackear la animacion default con Web Animations API (ej: circular reveal).
View Transition Types (SPA)
Para condicionar animaciones CSS segun el tipo de navegacion:
document.startViewTransition({
update: () => updateDOM(),
types: ['slide-forward'],
});
:active-view-transition-type(slide-forward) {
&::view-transition-old(root) { animation: slide-to-left 0.3s; }
&::view-transition-new(root) { animation: slide-from-right 0.3s; }
}
Features Nuevas
match-element
Auto-naming para listas largas sin nombrar cada elemento:
.card {
view-transition-name: match-element;
view-transition-class: card;
}
view-transition-class
Agrupar multiples named elements bajo una misma animacion:
::view-transition-group(*.card) {
animation-duration: 0.3s;
}
Scoped View Transitions (experimental)
element.startViewTransition() en vez de document.startViewTransition(). Permite multiples transiciones simultaneas en distintos subtrees. No production-ready aun.
Integracion con React y Next.js
Para patrones detallados de integracion, leer references/react-nextjs.md.
Resumen rapido:
- React canary tiene
<ViewTransition>component nativo - Next.js tiene flag experimental
viewTransition: trueen next.config.js - next-view-transitions (Shu Ding) es la solucion pragmatica para App Router
- Para control imperativo, usar un hook wrapper sobre
startViewTransition()
Patrones de Animacion
Para patrones comunes (slide direction, product gallery morph, modal transitions), leer references/animation-patterns.md.
Debugging
- Chrome DevTools > Animations panel para scrubear transiciones
- Bajar velocidad al 10% para inspeccionar
- Pseudo-elementos visibles en Elements panel durante la transicion
Problemas comunes:
- Transicion no anima → Chequear names duplicados visibles
- Transicion se aborta → El callback throweo un error
- Overflow roto → Contenido en transicion rompe containment, usar CSS containment strategies
- React skipea transicion → Hay un
flushSyncen el medio
Checklist Pre-Deploy
[ ] Feature detection con fallback
[ ] prefers-reduced-motion implementado
[ ] Duracion < 500ms
[ ] view-transition-names unicos por estado
[ ] Elementos nombrados limitados (no 50+)
[ ] Callback liviano
[ ] animation-fill-mode: forwards en custom animations
[ ] MPA: @view-transition en AMBAS paginas + same-origin
[ ] Testeado en Chrome, Safari Y Firefox
[ ] No meta tag obsoleto
[ ] React: sin flushSync en transitions