View Transitions API
View transitions morph the old UI into the new one when the DOM updates -- list to detail, theme swap, filtered gallery -- without hand-rolling FLIP math for every case.
This page expands the one-line mention in Modern CSS Features. Focus is same-document transitions (the common SPA / progressive-enhancement path). Cross-document (MPA) support is noted as a watch item.
Same-document flow
- Call
document.startViewTransition(() => { /* update the DOM */ }). - The browser captures old and new states, builds a transition tree of pseudo-elements, and runs animations.
- You customise duration, easing, and which elements morph via CSS.
const update = () => {
main.dataset.view = 'detail';
// …swap content, classes, etc.
};
if (document.startViewTransition) {
document.startViewTransition(update);
} else {
update();
}
Always keep a non-animated path. The API is progressive enhancement around a plain DOM update.
Naming elements that should morph
By default the whole page cross-fades as one snapshot. Give persistent pieces a shared
view-transition-name so the browser can animate them independently:
.card-thumb {
view-transition-name: hero;
}
.detail-hero {
view-transition-name: hero;
}
Names must be unique in the visible tree during a transition (like id). Use none to opt out.
.skip-transition {
view-transition-name: none;
}
Pseudo-elements
The browser builds a tree rooted at ::view-transition. Important pieces:
| Pseudo-element | Role |
|---|---|
::view-transition | Root overlay for the transition |
::view-transition-group(name) | Group for one named transition |
::view-transition-image-pair(name) | Holds old and new snapshots |
::view-transition-old(name) | Snapshot of the old state |
::view-transition-new(name) | Snapshot of the new state |
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 280ms;
animation-timing-function: ease;
}
::view-transition-group(hero) {
animation-duration: 360ms;
}
You can replace the default fade/morph with custom @keyframes on these pseudos. Keep motion short and purposeful.
Root vs named groups
rootcovers the document (or the transitioning root) when you have not named everything.- Named groups (
hero,title, …) let list thumbnails expand into detail media while the rest of the page fades more quietly.
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 200ms;
}
::view-transition-group(hero) {
animation-duration: 400ms;
}
Reduced motion
Respect prefers-reduced-motion. Options:
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Or skip calling startViewTransition when the user prefers reduced motion:
const motionOk = !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (motionOk && document.startViewTransition) {
document.startViewTransition(update);
} else {
update();
}
When to use it
Good fits
- Soft navigations inside a page (tabs, multi-step flows, list → detail)
- Theme toggles that should feel continuous
- Filtering / sorting where items rearrange
Weaker fits
- Tiny hover micro-interactions (use transitions)
- Scroll-linked scrubbing (use scroll-driven animations)
- Cases where you need frame-by-frame control of every pixel (custom WAAPI / canvas)
Cross-document (MPA) transitions -- watch
Browsers are adding cross-document view transitions for multi-page apps (opt-in via meta / CSS, plus matching
view-transition-names across pages). Treat MPA transitions as an enhancement to verify against current MDN and
Baseline status for your audience. Same-document startViewTransition remains the reliable teaching path today.
Gotchas
- Unique names. Two visible elements with the same
view-transition-namebreak or confuse the transition. - Capture cost. Huge trees or heavy effects during capture can jank; keep snapshots simple.
- Async updates. Prefer completing DOM work inside the
startViewTransitioncallback (or its update promise pattern) so old/new captures match what you intend. - Focus and a11y. Still manage focus after the update; the animation does not replace good navigation hygiene.
- Stacking / overflow. Named elements with complex overflow or transforms may snapshot differently than you expect -- test in DevTools.
Browser support
Same-document View Transitions are widely available in Chromium and have been expanding elsewhere. Gate on
document.startViewTransition and check Can I use / MDN before requiring
them.
Related reading
- Transitions & Animations -- classic CSS motion
- Scroll-Driven Animations -- scroll as timeline, not DOM swaps
- Modern CSS Features -- survey hub
- MDN: View Transition API
- Inspiration: modern-css.com page transitions