Skip to content
Velora
Showcase
Navigation

Page Transitions

Native cross-document View Transitions — no animation runtime JavaScript. CSS and thevl-page-transition attribute only.

Test here — page transitions

Test here

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:

ValueOut animationIn animationFeel
(default / velora)Blur + scale down + fadeBlur + scale up + fadeCinematic, asymmetric
wipeClip-path curtain closes upwardClip-path curtain opens downwardEditorial, theatrical
glideSlide left + fadeSlide from right + fadeApp-like, directional
irisCircular clip closes from centerCircular clip opens from centerDramatic, cinematic iris
cinemaBlur + brightness flash + scale + clip-insetBlur + scale + clip-inset + brightnessMost dramatic, premium
snapQuick down + slight tiltQuick from above + slight tiltSnappy, 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:

Classview-transition-nameKeyframe pair
.vl-vt-shared-herohero-titleTranslate + scale hero heading between pages
.vl-vt-shared-mediahero-mediaCross-fade with saturation + scale shift for images
.vl-vt-shared-navsite-navGlide nav from above on page enter
.vl-vt-shared-brandsite-brandSpring snap for brand logo between pages
.vl-vt-shared-cardshared-cardGlide 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

Supported

Chrome 111+, Edge 111+

In preview

Safari Technology Preview

Fallback

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>