View Transitions
Progressive same-document and cross-document transitions with semantic hash-link fallbacks.
Click between same-document views
Native hash links trigger the shipped router; shared transition names connect the avatar and heading between states.
<div data-view-transitions>
<nav aria-label="Profile views">
<a data-view-trigger href="#profile-overview">Overview</a>
<a data-view-trigger href="#profile-activity">Activity</a>
</nav>
<section data-view-page id="profile-overview" active>
<div class="profile-avatar">A</div><h2>Avery Gray</h2>
</section>
<section data-view-page id="profile-activity">
<div class="profile-avatar profile-avatar--square">A</div><h2>Recent activity</h2>
</section>
</div>
<style>
.profile-avatar { view-transition-name: profile-avatar; }
h2 { view-transition-name: profile-title; }
</style> Avery Gray
Workspace administrator
Recent activity
Invited two teammates · 4 minutes ago
More examples
Each example introduces a different part of the shipped API.
Native links are the cross-document API
The transition is progressive: navigation still works when the browser does not animate it.
@view-transition { navigation: auto; }
.product-title { view-transition-name: product-title; }
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*) { animation-duration: 0s; }
} Unique names connect matching elements.
The stylesheet supplies tokenized root animations and an optional view-router presentation layer. Browsers without the View Transitions API still receive immediate state changes.
Cross-document navigation
Opt both same-origin documents into navigation transitions with the CSS at-rule:
@view-transition { navigation: auto;}Then use ordinary links. No router JavaScript is required.
<a href="/products/42">View product</a>The root animation uses:
--view-transition-duration, default180ms--view-transition-easing--view-transition-hero-easing
The animation is removed when the user prefers reduced motion.
Same-document views
Use view-transitions, view-transition, [data-view-transitions], or .view-transitions for the router host. Pages support view-page, [data-view-page], and .view-page.
The semantic data-host form uses native hash links and sections:
<div data-view-transitions> <nav aria-label="Dashboard views"> <a data-view-trigger href="#summary">Summary</a> <a data-view-trigger href="#activity">Activity</a> </nav>
<section data-view-page id="summary" active> <h2>Summary</h2> <p>12 open tasks.</p> </section>
<section data-view-page id="activity"> <h2>Activity</h2> <p>Recent project changes.</p> </section></div>Without JavaScript, the hash target becomes visible through :target. Keep one page marked active as the server-rendered initial view.
Add the enhancement for animated updates, history synchronization, current-link state, and focus movement:
<script type="module" src="/view-transition.js"></script>Shared elements
Set view-transition-name in CSS on an element present in both states:
.profile-avatar { view-transition-name: profile-avatar;}Names must be unique within the rendered state. The property is CSS; view-transition-name is not an HTML attribute.
The shipped hero-element group receives the shared hero easing:
.product-image { view-transition-name: hero-element;}For other names, style the matching pseudo-element directly:
::view-transition-group(profile-avatar) { animation-timing-function: var(--view-transition-hero-easing);}Accessibility
Use native links or buttons for triggers. Hash links provide the strongest no-JavaScript fallback. The enhancement adds aria-current="page" to the active trigger and aria-controls when it is absent.
Give view pages meaningful headings. The script focuses the activated page with tabindex="-1" after its DOM update, including after the View Transition callback completes.