View Transitions API CSS Angle
CSS view transitions — ::view-transition pseudo-tree, view-transition-name, default crossfades, and reduced-motion-safe page morphs.
- css
- view-transitions
The View Transitions API snapshots the old and new UI states and crossfades or morphs between them. From the CSS angle you name elements, style the pseudo-element tree, and opt out under reduced motion. JS starts the transition (document.startViewTransition); CSS defines how it looks.
Docs: MDN View Transitions API, view-transition-name, Chrome guide.
Minimal same-document flow (JS + CSS)
function go(updateDom) {
if (!document.startViewTransition) {
updateDom();
return;
}
document.startViewTransition(updateDom);
}
/* Default root transition is a crossfade */
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 200ms;
}
Naming elements to morph
.hero-title {
view-transition-name: hero-title;
}
.hero-media {
view-transition-name: hero-media;
}
Matching names between old and new states enable shared-element style transitions. Names must be unique in a document state (one element per name).
::view-transition-old(hero-media),
::view-transition-new(hero-media) {
animation-duration: 280ms;
animation-timing-function: ease;
height: 100%;
object-fit: cover;
}
Pseudo-element tree
Key players:
::view-transition::view-transition-group(name)::view-transition-image-pair(name)::view-transition-old(name)::view-transition-new(name)
You animate these like normal pseudo-elements — opacity, transform — carefully.
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
::view-transition-old(root) {
animation: 160ms ease both fade-out;
}
::view-transition-new(root) {
animation: 160ms ease both fade-in;
}
MPA / cross-document (where supported)
@view-transition {
navigation: auto;
}
Opt-in for same-origin navigations in supporting browsers. Still progressive enhancement.
Reduced motion and fallbacks
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Always ensure the DOM update path works with no view transitions API.
Practical product use
- Soft navigation between list and detail (shared thumbnail name)
- Theme toggle crossfade of
root - Tab panels with restrained opacity transitions
Avoid circus morphs on dense data tables.
Interview out-loud
“View Transitions snapshot old and new states and animate between them. I set view-transition-name on shared elements, style ::view-transition-old/new, and call document.startViewTransition when supported. CSS controls duration and easing; reduced motion disables animations. It’s progressive enhancement for navigation and state changes.”
Footguns
- Duplicate
view-transition-namevalues in one frame. - Animating huge full-page screenshots on low-end devices.
- Forgetting fallback when API missing.
- Ignoring focus management after the DOM swap.
- Conflict with other full-screen fixed layers and z-index during transition.
List → detail shared element
.thumb { view-transition-name: var(--vt-name); }
/* set --vt-name uniquely per item via inline style or attribute selector */
function openItem(id) {
const run = () => router.navigate(`/items/${id}`);
if (document.startViewTransition) document.startViewTransition(run);
else run();
}
Ensure the detail page image uses the same view-transition-name for the morph. Clear names when leaving the view if you recycle DOM nodes. Test reduced motion and missing API paths every time you add a transition.
Debugging morph glitches
If a shared element jumps: check that names match exactly, that only one node owns the name per state, and that images have similar aspect ratios. Huge size deltas can look like a zoom war — soften with shorter animations or skip shared elements and crossfade root only. Always verify keyboard focus after transition; the API does not manage focus for you.
Related
- CSS animations keyframes
- prefers-reduced-motion
- transform and opacity performance
- Stacking contexts and z-index
Further reading
Related guides
- Accent Color and Form ControlsUse accent-color to theme native checkboxes, radios, ranges, and progress — what it paints, what it skips, and when to custom-style instead.
- Auto-fit vs Auto-fillauto-fit collapses empty tracks; auto-fill keeps them. Use both with minmax for responsive card grids without media-query soup.
- BEM Naming MethodologyBlock, Element, Modifier naming for CSS — how it limits specificity wars, what to name, and when utility or CSS modules beat strict BEM.
- blend-mode Basicsmix-blend-mode vs background-blend-mode — how pixels combine, isolation, and the stacking/readability traps in product UI.
- box-sizing border-boxWhy border-box makes width include padding and border, the global reset pattern, and the layout bugs content-box still causes.