View Transitions
A transition animates a change of a property: a color, a size. But when the page content is replaced entirely — new text, a different order of cards, another page — there is nothing to animate. View Transitions solve this: the browser remembers how the page looked before the change and after it, and smoothly turns one into the other.
The first transition
A transition is started from JavaScript with the document.startViewTransition() method. It is given a function that changes the page — the browser does the rest. Press the button:
<style>
.tip { padding: 16px; background: lavender; }
</style>
<button onclick="nextTip()">Next tip</button>
<p class="tip" id="tip">Drink more water.</p>
<script>
const tips = ["Drink more water.", "Take breaks.", "Go for a walk every day."];
let index = 0;
function nextTip() {
document.startViewTransition(() => {
index = (index + 1) % tips.length;
document.querySelector("#tip").textContent = tips[index];
});
}
</script>The old text fades out and the new one fades in. Without the transition it would change instantly.
Transition pseudo-elements
For the duration of the transition, the browser builds a tree of pseudo-elements above the page — snapshots of the old and new view. They are styled in CSS like regular elements:
| Pseudo-element | What it is |
|---|---|
::view-transition | the layer above the page where the transition runs |
::view-transition-group(name) | the group of one element, moves and resizes it |
::view-transition-old(name) | the snapshot before the change |
::view-transition-new(name) | the snapshot after the change |
By default the whole page is called root. Let us slow the transition down to one second:
<style>
.tip { padding: 16px; background: lavender; }
::view-transition-old(root),
::view-transition-new(root) { animation-duration: 1s; }
</style>
<button onclick="document.startViewTransition(() => document.querySelector('#tip').textContent = 'Take breaks.')">Change the tip</button>
<p class="tip" id="tip">Drink more water.</p>view-transition-name
The view-transition-name property gives an element its own name. Such an element is animated separately from the page: the browser smoothly moves it from the old place to the new one and changes its size:
<style>
.stage { display: flex; height: 120px; padding: 8px; background: whitesmoke; }
.ball { width: 40px; height: 40px; border-radius: 50%; background: tomato; view-transition-name: ball; }
.stage.moved { justify-content: flex-end; align-items: flex-end; }
.stage.moved .ball { width: 80px; height: 80px; }
</style>
<button onclick="document.startViewTransition(() => document.querySelector('#stage').classList.toggle('moved'))">Move</button>
<div class="stage" id="stage"><div class="ball"></div></div>The class switches instantly, yet the ball still flies and grows — the browser built this animation.
Browser support
Transitions within a page work in all major browsers since late 2025 — Firefox added them last. For older browsers, check whether the method exists and change the page without animation:
<button onclick="update()">Update</button>
<p id="text">Before</p>
<script>
function update() {
const change = () => document.querySelector("#text").textContent = "After";
if (!document.startViewTransition) {
change();
return;
}
document.startViewTransition(change);
}
</script>Transitions between pages
The same effect is possible when following a regular link to another page of the site — without JavaScript. To get it, the CSS of both pages contains the rule @view-transition { navigation: auto; }. The browser links the old and the new page, and elements with the same view-transition-name fly to their new place. This cannot be shown in the example window: it takes two real pages of one site. At the time of writing, such transitions are supported in Chrome, Edge and Safari; in other browsers the page simply opens without animation.
Common mistake
The same name on two elements. view-transition-name must be unique on the page: if a name repeats, the browser cancels the animation, and the page changes abruptly:
<style>
.box { width: 60px; height: 40px; margin: 4px 0; background: steelblue; view-transition-name: box; }
.wide { width: 200px; }
</style>
<button onclick="document.startViewTransition(() => document.querySelectorAll('.box').forEach(b => b.classList.toggle('wide')))">Change</button>
<div class="box"></div>
<div class="box"></div>The blocks widen without animation. If there are many elements, give each one its own name: card-1, card-2 and so on.