Transitions✨ Premium

Cross-Document Page Morph

Two real HTML pages and a plain link: the list page thumbnail grows into the article page header while the browser switches pages — @view-transition { navigation: auto; } and the same view-transition-name on both sides, zero JavaScript, a 0.5 s morph going forward and on the back button, a normal page load wherever the browser can't do it.

View TransitionsMPA0 JS

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Transitions has 52 effects, including 9 free. Explore the category →

3 usage examples

Contents to poem — static site of a poetry review — Cross-Document Page Morph example 1

① Contents to poem — static site of a poetry review

WhenThe reader browses the contents of the autumn issue and clicks a poem: the contents page gives way to the poem page, after a plain link.
WhyThe site stays made of static pages; the contents illustration morphs into the poem header with no framework and no JavaScript, and the back button returns the image to the contents.
SettingsThree 56 px tall thumbnails at the bottom of the contents, a 104 px header on the poem page; one name per poem (x8a-ill-maree, -seigles, -oiseaux); poem.html holds all three poems and the anchor (#maree) picks which one shows, like the code you get; illustrations in CSS gradients, Georgia on cream paper.
Practitioner directory to a midwife's page — health center website — Cross-Document Page Morph example 2

② Practitioner directory to a midwife's page — health center website

WhenA patient looks for who to see for her pregnancy follow-up: in the directory, she opens the midwife's page, with her hours and the booking button.
WhyThe portrait carries over from one page to the next: the feel of an app, with pages any host can serve, and an address to share for each practitioner.
Settings3:4 portrait in the directory card and 132 × 176 px on the practitioner page: same proportions, the morph scales up without cropping; one page per practitioner (midwife.html), no anchor; only this portrait has a view-transition-name, the other cards stay still in the crossfade.
Route list to a route page — e-bike rental website — Cross-Document Page Morph example 3

③ Route list to a route page — e-bike rental website

WhenThe visitor compares the routes starting from the shop and opens "Gorges and Lavender": distance, elevation gain, duration, recommended battery and booking.
WhyThe route map grows into the next page's header and keeps the visitor oriented; without browser support, navigation stays normal.
SettingsInline SVG map with preserveAspectRatio="xMidYMid slice": 118 px tall in the list card, 124 px as a 500 px wide header; the code's object-fit: cover cropping avoids any distortion during the morph; dedicated page gorges-and-lavender.html; the route card stands out with an orange border.

How it works

The code is two files that sit side by side: page-liste.html (three cards, each a real <a href="page-article.html#natif"> link) and page-article.html (three <article> elements). Both pages open their stylesheet with the same rule, @view-transition { navigation: auto; }: it takes both sides opting in for the browser to animate the switch. On click, it takes a snapshot of the list page, loads the article page, looks in both for elements with the same view-transition-name — the .xdvt-thumb--natif thumbnail and the .xdvt-hero--natif header are both named xdvt-hero-natif — and slides and grows one into the other in 0.5 s, with a cubic-bezier(.2, .8, .2, 1) curve; the rest of the page crossfades. Not a single line of JavaScript: the JS field is empty, everything lives in the CSS.

The thumbnail is wide and short (110 px tall in a card at least 200 px wide), the header taller (220 px across the full width): left alone, each image would keep its proportions and spill over the top and bottom during the morph. So ::view-transition-old(*) and ::view-transition-new(*) get block-size: 100% and object-fit: cover, and ::view-transition-image-pair(*) gets overflow: clip: each image fills the growing frame, cropped. Each card has its own name (xdvt-hero-natif, -zero, -mpa), because a name must be unique within a page: two visible elements with the same name and Chromium cancels the whole morph (measured, with an "Unexpected duplicate view-transition-name" message in the console). On the article page, the three headers carry the three names without conflict, because only the displayed article is captured; the two thumbnails with no partner fade out in place.

The article page holds all three articles and the anchor picks which one shows: .xdvt-article is hidden, the first one shows by default, :target shows the one the address points to (#zero) and :has() then hides the first. That is what lets the effect ship as only two files. On a real site, make one page per article (article-natif.html…): a single <article>, no more :target rules, each page gets its own title and address,. Going back needs no code: the "← Retour à la liste" link and the browser back button both replay the morph in reverse, the header turns back into the thumbnail (measured in Chromium 151 and WebKit 26.5). The pages must be served over HTTP or HTTPS, from the same origin: a browser that doesn't know these transitions ignores the rule and loads the page as usual. The demo text in both files is in French: replace it with your own content.

Accessibility

  • Reduced motion: both pages declare @media (prefers-reduced-motion: reduce) { @view-transition { navigation: none; } }. Measured under emulation in Chromium 151 and WebKit 26.5: no animation, the article page replaces the list at once, going forward and back.
  • Real navigation: each switch is an ordinary page change. The screen reader announces the new page title, focus starts again at the top of the document, history, the back button, opening in a new tab and sharing the address all work with nothing to manage. The morph snapshot is purely visual. Give each article page a <title> that names the article: the anchor version has a single one for all three ("Le carnet — article"), one more reason to make one page per article.
  • Keyboard: the cards are real links, reachable with Tab (in Safari, Option + Tab or the "Press Tab to highlight each item" setting), with a 2 px white outline on focus (:focus-visible). Enter on a card plays the same morph as a click (measured in Chromium and WebKit).
  • Contrast: the cards' secondary text (#a3a9c9 on #161b36) is 7.29:1, the overline (#8a90b0 on #0d1124) 5.96:1, the article text 11.9:1. The thumbnail gradients carry no text; if you write on them, white drops to 3.09:1 on the blue #6d8bff and to 1.25:1 on the mint #5cffd8: put the text on a dark band.

Browser compatibility

CSS only: @view-transition, view-transition-name, the ::view-transition-* pseudo-elements, :target and :has(); no JavaScript, no dependency. Measured with Playwright, pages served over HTTP from the same origin: morph going forward, on the back link and on the browser back button in Chromium 151 and WebKit 26.5 (also inside a same-origin iframe); Firefox 153 switches pages normally, with no animation and no error, even though it already supports transitions within a single page. No morph between two different origins (measured between localhost and 127.0.0.1), nor inside a sandbox iframe without allow-same-origin. With files opened by double-click (file://), WebKit doesn't play the morph and Chromium did in our test: don't rely on it, test with a small local server. Public support for cross-document transitions: Chrome and Edge 126, Safari 18.2.

Chrome 126+✓ Full
Edge 126+✓ Full
Safari 18.2+✓ Full
Firefox (153 measured)✓ Normal navigation, no morph
Mobile iOS 18.2+ / Android Chrome 126+✓ Full
Older browsers✓ Normal navigation, no morph

A browser that doesn't know @view-transition ignores the rule: the link loads the article page like any other link, with the same content, the same address and the same history. Same thing under reduced motion (navigation: none), between two different origins, or if two visible elements on a page share a name. Nothing breaks, only the morph is missing.

The code

HTML structure to paste into your page (the CSS + JS come with Premium):

index.html — structure
<!-- page-liste.html: @view-transition { navigation: auto; } in the style -->
<body class="xdvt-page">
  <main class="xdvt-wrap">
    <p class="xdvt-h">…</p>
    <h1 class="xdvt-title">…</h1>
    <div class="xdvt-grid">
      <a class="xdvt-card" href="page-article.html#natif">
        <span class="xdvt-thumb xdvt-thumb--natif"></span>
        <span class="lbl">…</span>
        <span class="meta">…</span>
      </a> … (3 cards: #natif, #zero, #mpa)
    </div>
  </main>
</body>

<!-- page-article.html: same rule, same names on the headers -->
<body class="xdvt-page">
  <main class="xdvt-wrap">
    <p class="xdvt-h">…</p>
    <a class="xdvt-back" href="page-liste.html">← …</a>
    <article class="xdvt-article" id="natif">
      <div class="xdvt-hero xdvt-hero--natif"></div>
      <h1>…</h1>
      <p>…</p>
    </article> … (3 articles: natif, zero, mpa)
  </main>
</body>
Unlock the full code — $9.90/mo incl. tax

The full HTML + CSS + JS of Cross-Document Page Morph, ready to paste — plus all 840 effects. Or $299 once, lifetime access.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Duration and curve (CSS — ::view-transition-group(*)) 0.5 s, cubic-bezier(.2, .8, .2, 1) 0.3 s gives a brisk switch, beyond 0.8 s visitors wait for the page. The * targets every group, page crossfade included; to tune a single element, name it: ::view-transition-group(xdvt-hero-natif).
Shared elements (CSS — view-transition-name) one name per card: xdvt-hero-natif, -zero, -mpa Same name on the thumbnail and on the header of the article it opens, unique within each page. To slide the title too, give a second name (xdvt-title-natif) to the card label and to the article's <h1>.
Cropping during the morph (CSS — ::view-transition-old(*), ::view-transition-new(*)) block-size: 100%, object-fit: cover, image-pair with overflow: clip Without these rules, each image keeps its proportions and spills out of the frame when the thumbnail and the header have different shapes. object-fit: contain shows the whole image, with empty bands.
Rest of the page crossfade (CSS — ::view-transition-old(root), ::view-transition-new(root)) browser crossfade, 0.5 s animation: none on both removes the crossfade: the rest of the page switches at once and only the shared element moves. Keep the site header identical on both pages: it then looks still.
Reduced motion (CSS — @media (prefers-reduced-motion: reduce)) @view-transition { navigation: none; } Navigation with no animation at all. For a very short fade instead, keep navigation: auto in this block and set ::view-transition-group(*) to animation-duration: .15s.
Picking the article (CSS — :target and :has()) one article page holding three articles, the first by default On a real site, one page per article: a single <article>, the four .xdvt-article rules removed, the link becomes article-natif.html. The names don't change.

FAQ

The browser only animates the switch between two pages from the same origin (same protocol, same domain, same port), served over HTTP or HTTPS. Opened as file://, the pages have no reliable origin: WebKit doesn't play the morph, Chromium did in our test, but nothing guarantees it. To test, start a small server in the folder (python3 -m http.server, then http://localhost:8000/page-liste.html) or use your editor's. Same rule online: no morph between www. and the bare domain, nor inside a sandbox iframe without allow-same-origin.
The same browser API, two uses. View Transitions Page Reveal animates a grid and a detail view within a single document, with document.startViewTransition() and 2.5 KB of JavaScript: useful in a single-page app, and it also plays in Firefox. Cross-Document Page Morph animates between two real pages, with two CSS rules and no script: each page keeps its address, its history and its search ranking, but Firefox (153 measured) does a normal page load for now. Pick this one for a site made of pages, the other for an interface that doesn't reload.
No. Firefox 153 already reads view-transition-name and can animate within a single page; it only ignores @view-transition between two documents, with no error. The day it applies it, the same two files will morph there as they do in Chrome and Safari.