wandres.dev
VIEW TRANSITIONS III · Entre documentos

La regla @view-transition

Activar transiciones entre documentos con dos líneas de CSS, las condiciones exactas que debe cumplir la navegación, y el estado real de soporte.

⏱ 16 min

Durante una década, la única forma de animar un cambio de página fue no cambiar de página: interceptar el clic, pedir el contenido por red, sustituirlo en el DOM y mantener a mano un router, un historial y una gestión de foco que el navegador ya sabía hacer. La regla @view-transition elimina esa necesidad para el caso de las transiciones visuales. Dos líneas de CSS en cada página, sin una sola línea de JavaScript, y una navegación normal —con su URL, su historial y su botón de atrás— se anima.

🎯 Al terminar esta lección sabrás
  • Activar transiciones entre documentos con la regla y su descriptor.
  • Enumerar las condiciones que debe cumplir una navegación para transicionar.
  • Situar el estado de soporte real y decidir en consecuencia.
  • Reconocer las navegaciones que nunca van a transicionar y por qué.

Dos líneas en cada documento

@view-transition {
  navigation: auto;
}

Eso es todo. La regla tiene que estar en las dos páginas: la que se abandona y la que se carga. Si solo una de ellas la declara, no hay transición. La opción es deliberadamente bilateral: una página no puede imponer una transición a otra, y eso evita que un enlace desde un sitio agresivo altere cómo se presenta el tuyo.

El descriptor navigation acepta dos valores: auto, que activa la transición, y none, que la desactiva. none sirve para desactivar en una sección concreta lo que se activó globalmente, aunque en la práctica se usa poco porque la regla no cascadea como una declaración normal.

Existe un segundo descriptor, types, que declara los tipos de transición activos para la navegación:

@view-transition {
  navigation: auto;
  types: deslizar, adelante;
}

Como los tipos se limpian solos al terminar la transición, funcionan bien incluso con la caché de retroceso y adelante del navegador, que restaura páginas ya construidas. La alternativa desde JavaScript —añadir tipos en los eventos de intercambio y revelado— es más flexible porque puede decidir en función del destino, y es el tema de la lección siguiente.

Las condiciones exactas

La transición no ocurre en cualquier navegación. Las condiciones son estas y merece la pena conocerlas porque explican los casos en que “no funciona y el CSS está bien”:

Mismo origen, sin redirecciones a otro origen. Una navegación a otro dominio no transiciona nunca. Tampoco una que pase por una redirección de origen distinto, aunque acabe volviendo al tuyo. La razón es de seguridad: la captura del estado antiguo es una imagen de tu página, y no puede quedar disponible durante el renderizado de un documento de otro origen.

El tipo de navegación debe ser traverse, push o replace. Es decir: retroceder o avanzar en el historial, o navegar a una URL nueva. Quedan fuera las recargas, que son de tipo reload.

En push y replace, la navegación tiene que originarse en el contenido de la página. Un clic en un enlace, un envío de formulario, una llamada a la API de navegación. No transicionan las navegaciones iniciadas desde la interfaz del navegador: escribir una URL en la barra de direcciones, usar un marcador, abrir desde otra aplicación. Es coherente con la idea de que la transición representa una continuidad dentro del sitio, y esa continuidad no existe cuando el usuario llega de fuera.

Fíjate en que las navegaciones de tipo traverse —los botones de atrás y adelante— transicionan aunque se inicien desde la interfaz del navegador. Es la excepción deliberada, porque volver atrás es precisamente el caso donde la continuidad visual más aporta.

⚠️
Firefox no lo tiene y no está a la vuelta de la esquina

Las transiciones de mismo documento están en los tres motores desde 2025 y son Baseline. Las de entre documentos, no: están en Chromium desde la 126 y en Safari desde la 18.2, y Firefox no las ha implementado. Hay un seguimiento público del trabajo, pero a día de hoy la regla @view-transition está catalogada como disponibilidad limitada. La diferencia práctica respecto a las animaciones de scroll es que aquí el modo de fallo es intachable: sin soporte, la navegación ocurre exactamente igual, sin animación. Es la mejora progresiva perfecta, y por eso se puede adoptar hoy sin ninguna reserva.

Lo que hace el navegador por debajo

Vale la pena entender la secuencia porque explica una restricción que aparece en la lección sobre lo que puede fallar.

Cuando el usuario pulsa un enlace en una página que ha declarado la regla, el navegador no navega inmediatamente. Primero comprueba si el documento de destino también ha declarado la regla, lo que implica haber empezado a recibirlo y haber procesado su CSS. Después captura el estado antiguo, sustituye el documento, espera a que el nuevo esté listo para renderizarse, captura el estado nuevo, y anima.

De ahí salen dos observaciones que no son obvias. La primera: el estado antiguo se captura antes de descargar la página entera, así que la captura ocurre pronto y no depende de lo que tarde el resto del contenido. La segunda: el navegador tiene que esperar a que el documento nuevo esté listo, y esa espera es el punto donde una página lenta convierte la transición en una pantalla congelada.

El resto del ciclo es idéntico al de mismo documento: el mismo árbol de pseudo-elementos, los mismos nombres, las mismas animaciones por defecto, el mismo CSS. Todo lo que aprendiste en los niveles 18 y 19 se aplica sin cambios. La única diferencia real está en cómo se coordinan los nombres, porque ahora los dos estados viven en documentos distintos que no comparten nada.

Esto no es una API nueva, es la misma API con el navegador haciendo de orquestador

La forma más productiva de pensar en las transiciones entre documentos es esta: el navegador está llamando a startViewTransition por ti, y tu callback es la propia navegación. Todo lo demás es idéntico, y esa identidad es lo que hace que el conocimiento sea transferible: los pseudo-elementos, las promesas, skipTransition(), los tipos, view-transition-class. Si sabes personalizar una transición de mismo documento, ya sabes personalizar una entre documentos. Ahora la consecuencia que hay que digerir, y que reconfigura cómo se plantea un proyecto entero: la razón principal para construir una aplicación de página única acaba de desaparecer. Durante años, la respuesta a “queremos transiciones bonitas entre páginas” fue “entonces necesitas una SPA”, y con ella llegaba un router propio, gestión de historial, gestión de foco, gestión de scroll, un estado de carga que no es el del navegador, y una arquitectura entera que existía para sostener un efecto visual. Con @view-transition, una web servida como HTML estático, con enlaces normales, obtiene el mismo resultado con dos líneas de CSS y sin renunciar a nada de lo que el navegador hace bien. Eso no significa que las SPA no tengan sentido —lo tienen cuando hay estado compartido real entre vistas, edición sin guardar, o conexiones vivas—, significa que la transición visual dejó de ser un argumento a favor, y conviene borrarlo de la lista cuando se decida la arquitectura del próximo proyecto.

Un caso mínimo completo

Para verlo funcionando de verdad hace falta muy poco. Dos ficheros HTML servidos desde el mismo origen, cada uno con esto en su hoja de estilos:

@view-transition {
  navigation: auto;
}

/* Personalizacion minima: un fundido algo mas lento y con curva */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 320ms;
  animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
}

/* La cabecera es la misma en las dos paginas: que no parpadee */
header {
  view-transition-name: vt-cabecera;
}

Con esas ocho líneas repetidas en las dos páginas, la navegación entre ellas hace un fundido de la página completa mientras la cabecera se queda fija, porque tiene su propio grupo y su geometría no cambia. El efecto es exactamente el que la gente monta con routers de cliente, y aquí es CSS estático sobre enlaces <a> normales.

Ese detalle de la cabecera es más importante de lo que parece y conviene entenderlo bien: sin el nombre, la cabecera forma parte de la captura de la raíz y se desvanece y reaparece con todo lo demás, produciendo un parpadeo en un elemento que no ha cambiado. Nombrar los elementos persistentes del layout —cabecera, navegación, pie— es el primer trabajo de cualquier transición entre documentos, y muchas veces es el único que hace falta.