view-transitions

CSS-only page transition effects with named groups, shared element morphing, animation presets, and a declarative init script.

Quick Start

Import the opt-in CSS utility. This single import gives you smooth crossfade transitions on all same-origin navigations:

@import "vanilla-breeze/utils/view-transitions.css";

That’s it for basic crossfade. The <main> element automatically gets a named transition group (main-content) so content fades smoothly between pages.

For shared element morphing, add data-vt-name to elements that should animate between pages:

<!-- Gallery page --> <a href="/products/123"> <img data-vt-name="product-123" src="thumb.jpg" /> <h2 data-vt-name="title-123">Product Name</h2> </a> <!-- Detail page - matching names morph smoothly --> <img data-vt-name="product-123" src="hero.jpg" /> <h1 data-vt-name="title-123">Product Name</h1>

The data-vt-name attribute requires the init script (included in all doc site layouts, or import view-transition-init.js).

Named Groups

Use data-vt to assign common transition names to page regions. Named elements persist or morph across navigations instead of crossfading.

Attribute Transition Name Use Case
data-vt="main"main-contentMain content area (auto-applied to <main>)
data-vt="header"page-headerSite header — persists across pages
data-vt="nav"site-navPrimary navigation
data-vt="sidebar"page-sidebarSidebar navigation — stays in place within sections
data-vt="hero"page-heroHero image or banner
data-vt="footer"site-footerSite footer
<header data-vt="header">...</header> <nav data-vt="sidebar">...</nav> <main> <!-- auto-named as main-content --> <div data-vt="hero">...</div> ... </main> <footer data-vt="footer">...</footer>

When navigating between pages with the same named group, the browser morphs the element in place rather than crossfading. This keeps headers, sidebars, and footers visually stable during navigation.

Shared Element Morphing

For unique per-element names (galleries, product cards, lists), use data-vt-name:

<!-- Each element gets a unique name --> <img data-vt-name="hero-1" src="thumb-1.jpg" /> <img data-vt-name="hero-2" src="thumb-2.jpg" /> <img data-vt-name="hero-3" src="thumb-3.jpg" /> <!-- On the detail page, the matching name morphs --> <img data-vt-name="hero-1" src="full-1.jpg" />

The init script (view-transition-init.js) sets view-transition-name from the attribute value. This replaces the need for inline style attributes.

How the Init Script Works

  1. On DOMContentLoaded, discovers all [data-vt-name] elements
  2. Sets el.style.viewTransitionName from the attribute value
  3. Marks each element with data-vt-name-init to prevent double-processing
  4. A MutationObserver handles dynamically added elements
// Already included in doc site layouts // For standalone pages: import 'vanilla-breeze/utils/view-transition-init.js';

Important: Each view-transition-name must be unique per page. Two elements with the same name on the same page will cause errors.

Animation Presets

Apply animation presets to transition groups using data-vt-class. This uses the CSS view-transition-class property to style transitions without targeting individual names.

Value Effect
slide-leftOld content slides out left, new slides in from right
slide-rightOld content slides out right, new slides in from left
scaleOld content scales down and fades, new scales up
fadePure opacity crossfade (no morphing)
noneInstant swap with no animation
<!-- Slide main content from the left --> <main data-vt-class="slide-left">...</main> <!-- Scale the hero section --> <div data-vt="hero" data-vt-class="scale">...</div> <!-- No animation for footer --> <footer data-vt="footer" data-vt-class="none">...</footer>

Tokens

Custom properties control transition duration:

Token Default Use Case
--view-transition-duration0.3sStandard transitions
--view-transition-duration-fast0.15sSubtle, quick transitions
--view-transition-duration-slow0.5sDramatic, deliberate transitions
:root { /* Slow down all transitions */ --view-transition-duration: 0.5s; }

Demos

Interactive examples showing view transitions in action. Open in a new tab for the full MPA navigation experience.

Gallery Morph

Card grid with shared element morphing to detail pages.

List → Detail

Vertical list with title and thumbnail morphing to a detail view.

Nav Persistence

Header and sidebar stay in place while main content transitions.

Animation Presets

Side-by-side comparison of slide, scale, fade, and none presets.

MPA vs SPA

Multi-Page Applications (MPA)

For traditional multi-page sites, use the CSS @view-transition rule. This is what the Vanilla Breeze utility provides:

@view-transition { navigation: auto; }

The browser automatically triggers transitions on same-origin navigations. No JavaScript needed.

Single-Page Applications (SPA)

For SPA routing (client-side navigation), use the JavaScript API:

document.startViewTransition(() => { // Update the DOM here updateContent(newContent); });

The same CSS transition names and animation presets work with both approaches.

Browser Support

Browser Same-document Cross-document (MPA)
Chrome / Edge111+126+
Safari18+18+
FirefoxPendingPending

In unsupported browsers, pages load normally without transitions. No errors, no broken functionality — true progressive enhancement.

Accessibility

  • Reduced motion: All transitions are disabled when prefers-reduced-motion: reduce is active (animation duration set to 0.01ms)
  • No content loss: View transitions are purely visual — content is always accessible regardless of browser support
  • Focus management: The browser handles focus during cross-document navigations
  • Performance: Transitions run on the compositor thread and don’t block the main thread

Best Practices

  • Unique names per page: Each view-transition-name must be unique. Duplicate names on the same page cause the transition to fail silently.
  • Name sparingly: Only name elements that should morph or persist. Unnamed elements participate in the default crossfade, which is usually fine.
  • Matching structure: Named elements on the source and destination pages should have similar visual size and position for the smoothest morph.
  • Test both directions: Transitions should look good navigating both forward and back.
  • Keep durations short: 200–400ms feels responsive. Longer durations can feel sluggish.
  • Avoid naming lists: Don’t give the same transition name to multiple list items. Name only the specific item being navigated to.