View Transitions: The Crossfade Between States Is Now One Call
The hardest animations to hand-build are transitions between layouts: a card grid re-sorting, an item deleted and the rest closing ranks, light mode to dark. FLIP technique, position math, clone elements... or:
document.startViewTransition(() => {
renderSortedGrid(); // any DOM mutation - your normal render code
});
The browser snapshots the old state, runs your callback, snapshots the new state, and crossfades between them. Your rendering logic doesn't change at all - the transition wraps it. That's the entire basic API.
Naming elements makes them travel
The default is a full-page crossfade. Give elements a view-transition-name and they animate independently - position, size and all:
.card.selected { view-transition-name: active-card; }
/* Customize any piece - these are real pseudo-elements */
::view-transition-old(active-card) { animation-duration: 0.3s; }
::view-transition-group(active-card) { animation-timing-function: cubic-bezier(0.2, 0, 0.2, 1); }
A thumbnail that grows into a detail view, a row that glides to its new sorted position - the browser matches old and new elements by name and animates the difference. Names must be unique per snapshot, so for lists, assign them dynamically (el.style.viewTransitionName = 'item-' + id) or scope them to the elements that move.
Progressive enhancement is built into the shape
function withTransition(update) {
if (!document.startViewTransition) return update(); // just do it, unanimated
return document.startViewTransition(update);
}
Because the callback is your normal update, unsupported browsers simply update instantly - no broken states, no forked logic. Support: Chromium 111+, Safari 18+, Firefox landing it now - which is exactly the profile where enhancement-not-requirement is the right posture. Wrap it once, use it everywhere.
Field notes
- The callback can return a promise (async renders welcome); the page freezes interaction briefly during the capture, so keep updates snappy.
- Respect prefers-reduced-motion - skip startViewTransition entirely when reduce is set.
- Theme toggles are the gateway drug: wrap your dark-mode switch and the whole page crossfades instead of flashing. Ten seconds of work, disproportionate polish.
- Same-document transitions are this API; cross-page transitions for MPAs exist behind @view-transition CSS in Chromium and are worth watching - your PHP site may get SPA-feeling navigation without the SPA.
For per-element, data-driven motion, element.animate() is still the tool. View transitions own the layer above: whole states, whole layouts, one function call.
Full-stack web developer sharing practical tutorials and building tools that ship.
Got something on your mind?
My inbox is open - no forms disappearing into the void here.
- Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
- Hire me for a project Have something custom in mind? Let's talk scope and timelines.
- Product support Bought something here? I'll help you get it running.
I usually reply within 1-2 business days.