Page Transitions
Native cross-document View Transitions — no animation runtime JavaScript. CSS and thevl-page-transition attribute only.
Test here — page transitions
Cross-document demo
This site runs vl-page-transition on <html>. Jump to Attributes or Effects to see the preset.
How to Enable
View Transitions require the browser's navigation opt-in. Velora ships this in its main CSS entry:
/* Already included with @import "@veloracss.io/css" */
@view-transition {
navigation: auto;
}Then set the transition preset on your root <html> element:
<!-- In your root layout -->
<html vl-page-transition="cinema">Omit the attribute (or use an unknown value) to get the default Velora transition.
Root Transition Presets
Applied to the <html> element via vl-page-transition:
| Value | Out animation | In animation | Feel |
|---|---|---|---|
| (default / velora) | Blur + scale down + fade | Blur + scale up + fade | Cinematic, asymmetric |
wipe | Clip-path curtain closes upward | Clip-path curtain opens downward | Editorial, theatrical |
glide | Slide left + fade | Slide from right + fade | App-like, directional |
iris | Circular clip closes from center | Circular clip opens from center | Dramatic, cinematic iris |
cinema | Blur + brightness flash + scale + clip-inset | Blur + scale + clip-inset + brightness | Most dramatic, premium |
snap | Quick down + slight tilt | Quick from above + slight tilt | Snappy, spring-based |
<!-- Default Velora transition -->
<html>
<!-- Wipe curtain -->
<html vl-page-transition="wipe">
<!-- Cinematic iris -->
<html vl-page-transition="iris">
<!-- Ultra-dramatic cinema -->
<html vl-page-transition="cinema">Transition Timing
Override the duration globally with a CSS custom property:
:root {
--vl-vt-root-duration: 0.4s; /* speed up all page transitions */
--vl-vt-shared-duration: 0.5s; /* speed up shared element transitions */
}Shared Element Transitions
Shared elements morph smoothly between pages when they carry a stable view-transition-name. Velora provides built-in classes with pre-defined names and matching keyframes:
| Class | view-transition-name | Keyframe pair |
|---|---|---|
.vl-vt-shared-hero | hero-title | Translate + scale hero heading between pages |
.vl-vt-shared-media | hero-media | Cross-fade with saturation + scale shift for images |
.vl-vt-shared-nav | site-nav | Glide nav from above on page enter |
.vl-vt-shared-brand | site-brand | Spring snap for brand logo between pages |
.vl-vt-shared-card | shared-card | Glide from card list → detail page |
Hero Morph Example
<!-- Page A: blog list -->
<article>
<img src="post.jpg" alt="" class="vl-vt-shared-media" />
<h2 class="vl-vt-shared-hero">Post Title</h2>
<a href="/posts/post-title">Read more</a>
</article>
<!-- Page B: blog post -->
<article>
<img src="post.jpg" alt="" class="vl-vt-shared-media" />
<h1 class="vl-vt-shared-hero">Post Title</h1>
<p>Article content...</p>
</article>When navigating from Page A to Page B, the image and heading morph seamlessly. The browser matches elements by view-transition-name.
Nav Persistence Example
<!-- In your shared navigation (both pages) -->
<nav class="vl-vt-shared-nav" aria-label="Site navigation">
<a href="/" class="vl-vt-shared-brand">
<img src="/logo.svg" alt="Velora" />
</a>
<!-- links ... -->
</nav>Card to Detail Example
<!-- List page: multiple cards -->
<!-- Use unique transition names to avoid conflicts -->
<div
class="vl-card vl-vt-shared-card"
style="view-transition-name: card-42;">
<!-- card content -->
</div>
<!-- Detail page -->
<article style="view-transition-name: card-42;">
<!-- expanded content -->
</article>Important: When you have multiple cards, override view-transition-nameper item with a unique slug or ID to prevent name collisions.
Shared Element Keyframes
Each shared class has its own keyframe pair. You can inspect and override them:
/* Hero title: translates + scales between pages */
::view-transition-old(hero-title) { animation-name: vl-vt-hero-old; }
::view-transition-new(hero-title) { animation-name: vl-vt-hero-new; }
/* Shared media: saturation cross-fade */
::view-transition-old(hero-media) { animation-name: vl-vt-media-cross; animation-direction: reverse; }
::view-transition-new(hero-media) { animation-name: vl-vt-media-cross; }
/* Custom duration for hero -->
::view-transition-old(hero-title),
::view-transition-new(hero-title) {
animation-duration: 0.75s;
}Accessibility
All view transition animations honor prefers-reduced-motion: reduce. Under reduced motion, all transition durations drop to 0.12s linear with no filter effects. The page still transitions, but nearly instantly.
/* Built into velora.transitions layer */
@media (prefers-reduced-motion: reduce) {
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.12s !important;
animation-timing-function: linear !important;
filter: none !important;
}
}Browser Support
Chrome 111+, Edge 111+
Safari Technology Preview
Firefox, Safari 17 — instant navigation, no transition effect
No polyfills or JavaScript required. Browsers that don't support View Transitions simply navigate instantly — the page works correctly in all cases.
Integration with Astro
Velora's transitions work perfectly with Astro's MPA mode. Add vl-page-transition to the <html> tag in your base layout:
---
// src/layouts/Base.astro
---
<html lang="en" vl-page-transition="cinema">
<head>
<!-- velora/css already includes @view-transition { navigation: auto } -->
</head>
<body>
<slot />
</body>
</html>