view transitions api

[check: …]
📖 Pair this live Tailwind demo with the companion guide (.md) — this page is the rendered ground truth. ↗ The React section covers the same API in a React context (wrapping setState in startViewTransition); this page shows the raw browser API + Tailwind pseudo-element styling.

the idea: snapshot → mutate → crossfade

The View Transitions API (document.startViewTransition(cb)) screenshots the current page, runs your callback (where you change the DOM), then crossfades the screenshot into the new live DOM. It is a browser primitive for animated state changes — no Framer Motion, no manual FLIP math. The default is a full-page crossfade; tag an element with view-transition-name and the browser morphs it between old and new boxes (a hero animation).

approachhow it animatescost
manual FLIP / JS lib measure old rect → mutate → measure new → invert → play (you write all of it) high — per-element bookkeeping, transforms, easing, cleanup
CSS transition only animates properties you can name on elements that persist cannot crossfade DOM that is replaced/removed
startViewTransition browser snapshots whole tree; crossfades old↔new; morphs named elements one JS call + a few CSS pseudo-element rules

The transition is visual only — the DOM inside the callback updates for real (event listeners, layout, accessibility tree). The screenshot is a transient overlay layered on top while the animation plays.

1 · list ↔ detail crossfade

Click an item. The whole stage is wrapped in startViewTransition(() => swap to detail). The browser screenshots the list, swaps to the detail DOM, and crossfades (::view-transition-old(root)::view-transition-new(root)). Click back to reverse. The status line captures the ViewTransition object the API returns, proving the machinery fired.

pick a release →

last transition: (click an item)

2 · hero morph — a named element animates between layouts

The cyan chip below carries view-transition-name: hero. It exists in BOTH the collapsed and expanded state, so when startViewTransition runs the browser captures the old box (small, top) and the new box (large, full-width) and morphs width, height, and position smoothly on ::view-transition-group(hero). You wrote zero transform math.

tap me

last hero transition: (click expand)

3 · feature detection + graceful fallback

Always feature-detect — Firefox ships this behind a flag. The idiom is a one-line wrapper: if the API exists, wrap the mutation; otherwise just run the mutation (instant swap, no animation). The status below is read live from typeof document.startViewTransition.

function swap(fn) { if (document.startViewTransition) { // feature-detect return document.startViewTransition(fn); // returns a ViewTransition object } fn(); // fallback: instant, no anim } swap(() => el.classList.toggle('expanded'));
browsersame-document VTnote
Chrome / Edge / Opera✅ 111+ (2023)Baseline Newly Available (2024)
Safari✅ 18+ (2024)shipped Sept 2024
Firefox⚠️ flag onlydom.viewTransitions.enabled — not on by default

feature-detect: …

cheat sheet — pseudo-elements & lifecycle

primitivewhat it isstyle it with
::view-transition-group(name) wrapper that morphs position + size for a named element animation-duration, animation-timing-function
::view-transition-old(name) static screenshot of the element before the change custom keyframes (fade/slide) — Tailwind arbitrary props work
::view-transition-new(name) live capture of the element after the change custom keyframes
view-transition-name: x opt an element INTO a named morph (must be unique per document at transition time) plain CSS, or Tailwind [view-transition-name:hero] arbitrary property
::view-transition (no name) root overlay container holding the whole snapshot tree rarely styled directly

ViewTransition object lifecycle (the JS side): .updateCallbackDone resolves when your callback's DOM mutation commits · .ready resolves when pseudo-elements are built and animations are about to start · .finished resolves when animations complete (rejects if .skipTransition() is called). Call transition.skipTransition() to bail out early.

Gold-check scans document.styleSheets for the ::view-transition-old/new(root) and view-transition-name rules defined in this page's <style> block — proving the transition CSS is loaded — and feature-detects the JS API.