View Transitions Page Reveal
The browser's real API, in a single page: document.startViewTransition() photographs the grid then the detail view, and the tapped thumbnail grows into the large visual in 0.55 s; going back returns it to its tile. A view-transition-name set just for the passage, 2.5 KB of JavaScript, instant display when the API is missing or the user reduces motion.
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



How it works
The .vt-demo frame (16/11 format, 14 px corners, #0d0d18 background) holds two views: the .vt-list grid of six tiles in 3 columns, and the .vt-detail view, hidden (display: none) as long as no tile is open. Each tile is a button: a .vt-thumb thumbnail whose background carries the image or gradient, a label on a blurred 35% black band, and the detail text in the data-text attribute. All content comes from the HTML: for your products, you write your tiles, the script creates none.
On click, the script copies the thumbnail's background (color, image, size, position) and its label into the large visual .vt-hero, then sets view-transition-name: vt-hero on the thumbnail AND on the large visual. The two are never displayed at the same time: in the before state, only the thumbnail is rendered, in the after state, only the large visual. document.startViewTransition() photographs the page, runs the swap (grid hidden, detail shown, focus on "← Back"), photographs the new page, then animates: the vt-hero group goes from the thumbnail's size and place to the large visual's, while the rest of the page cross-fades. A ::view-transition-group(vt-hero) rule sets this morph to 0.55 s on cubic-bezier(.4, 0, .2, 1); the rest of the fade keeps the default duration, 0.25 s. The names are removed at the end (t.finished).
Going back does the same in reverse: the name is set again on the large visual and on the open tile's thumbnail, and the visual shrinks into its tile, which gets focus back. Measured in Chromium 151, Firefox 153 and WebKit 26.5: all six tiles open their detail, and both the way there and back start a 550 ms vt-hero group. The ::view-transition-* pseudo-elements live on the document root: a CSS variable set on .vt-demo does not reach them, hence the rule written at the root. Several grids can live on the page, each with its own state; only one view transition runs at a time in a document.
Accessibility
- Focus: on opening, focus moves to "← Back"; on return, it goes back to the opened tile — measured in all three engines. With a keyboard: Tab to the tile, Enter to open it, then Enter again (focus is already on "← Back") to come back to the same place in the grid — measured in Chromium and Firefox, and in WebKit with Option+Tab, since Safari on Mac only stops on buttons with that key or the matching keyboard setting.
- Reduced motion: under
prefers-reduced-motion, the script does not call the API and swaps the views at once — measured in Chromium: no animation, detail shown on click. - Hidden view, absent view: the view not displayed is
display: none, so neither read nor reachable with the keyboard. The large visual's title is plain text: in a real page, put it in anh2so a screen reader knows the view changed. - Contrast: tile labels in white on a dark band, 19.3:1; detail text
#a1a1aaon#0d0d18, 7.5:1. The white title on the large visual depends on the image: 5.9:1 on violet#7b2ff7, but 1.5 to 2.2:1 on the light ends (mint green, cyan, coral); add a dark gradient at the bottom of the visual or a drop shadow to the text.
Browser compatibility
Same-document View Transitions API (document.startViewTransition, view-transition-name, ::view-transition-group) and about sixty lines of JavaScript, no dependency. Measured in Chromium 151, Firefox 153 and WebKit 26.5: the API is present in all three, 550 ms morph both ways, 250 ms fade of the rest. Without the API (measured in Chromium by removing it), the views swap instantly and focus follows the same way. Public support: Chrome and Edge 111, Safari 18, Firefox 144.
Without the View Transitions API or under reduced motion, the detail view replaces the grid at once, with the same content and the same focus. Without JavaScript, only the grid shows and tiles do not open: make each tile a link to a real page if the detail must stay reachable.
The code
HTML structure to paste into your page (the CSS + JS come with Premium):
<div class="vt-root">
<div class="vt-demo">
<div class="vt-list">
<div class="vt-grid">
<button class="vt-tile" type="button" data-text="…">
<span class="vt-thumb" style="background: …;"></span>
<span class="vt-tile-label">Aurora</span>
</button>
… (6 tiles)
</div>
</div>
<div class="vt-detail">
<div class="vt-hero"></div>
<div class="vt-detail-body">
<p class="vt-detail-text"></p>
<button class="vt-back" type="button">← Back</button>
</div>
</div>
</div>
</div>
The full HTML + CSS + JS of View Transitions Page Reveal, ready to paste — plus all 840 effects. Or $299 once, lifetime access.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Morph duration and curve (CSS — ::view-transition-group(vt-hero)) |
0.55 s, cubic-bezier(.4, 0, .2, 1) |
0.35 s for a snappy interface, 0.8 s for a gallery. The rule must stay at the root of the stylesheet, not under .vt-demo. |
Fade of the rest of the page (CSS — ::view-transition-old(root), ::view-transition-new(root)) |
browser cross-fade, 0.25 s | ::view-transition-old(root), ::view-transition-new(root) { animation-duration: .4s; } matches it to the morph; animation: none makes the detail appear at once around the growing visual. |
Tile content (HTML — .vt-thumb, .vt-tile-label, data-text) |
6 gradients, one text per tile | Put a photo as the thumbnail's background-image: the large visual picks it up (size and position included). For a richer page than data-text, fill the detail view from one template per tile. |
Grid (CSS — .vt-grid) |
3 columns, 12 px gap | repeat(auto-fill, minmax(140px, 1fr)) adapts to the width; the morph follows, since it starts from the thumbnail's real place. |
Large visual height (CSS — .vt-hero height) |
56% of the detail view | The larger it is, the more striking the morph; keep room for the text and the back button without scrolling. |
Transition name (JS — 'vt-hero') |
vt-hero |
It must be unique in the document during the passage: if another view transition on the page already uses this name, change it here and in the CSS rule. |
FAQ
transform, between two positions written in the stylesheet: the element never changes place in the DOM, and it works everywhere. View Transitions Page Reveal lets the browser do the work: the DOM really changes (the grid disappears, the detail appears), the API photographs both states and interpolates the thumbnail into the large visual, whatever their sizes and positions. Use Shared Element for an object sliding between two known slots, Page Reveal for a list → detail passage.typeof document.startViewTransition: without the API, it swaps directly, and the detail replaces the grid at once, with the same content and the same focus (measured by removing the API in Chromium). Nothing breaks; only the morph is missing. That is the case for Firefox before version 144 and Safari before version 18.@view-transition { navigation: auto; } in both pages' CSS and the same view-transition-name on the shared element of each page. That is the principle of Cross-Document Page Morph (fx-0800).