0 Pure CSS7 with JSPublished 2026-10-05Free · no sign-up
Seven same-page view transitions: a list that animates when sorted, a tab indicator that glides between tabs, tags that reflow smoothly as they are added and removed, a filtered grid, a theme change revealed as a circle, a thumbnail that grows into a lightbox and a number that slides when it changes.
Sorting a list animates every item from its old position to its new one. Each item gets a unique view-transition-name, the DOM is reordered inside startViewTransition, and the browser works out the movement; without support the list just reorders.
var ul = document.querySelector('.vt1 ul');
document.querySelectorAll('.vt1 button').forEach(function (b) { b.onclick = function () {
var k = b.dataset.k, items = Array.prototype.slice.call(ul.children).sort(function (x, y) { return k === 'price' ? x.dataset.price - y.dataset.price : x.dataset.name.localeCompare(y.dataset.name); });
var go = function () { items.forEach(function (li) { ul.appendChild(li); }); };
if (document.startViewTransition) document.startViewTransition(go); else go();
}; });
02. Tab Indicator That Glides
View TransitionsTabs
The underline of the active tab glides to the newly chosen tab because the indicator element is moved in the DOM inside a view transition and keeps the same view-transition-name. The tabs themselves follow the ARIA tabs pattern.
var tabs = document.querySelectorAll('.vt2 [role=tab]'), ind = document.querySelector('.vt2 .ind');
function sel(t) { var go = function () { tabs.forEach(function (x) { var on = x === t; x.setAttribute('aria-selected', on); x.tabIndex = on ? 0 : -1; }); t.appendChild(ind); }; if (document.startViewTransition) document.startViewTransition(go); else go(); t.focus(); }
tabs.forEach(function (t, i) { t.onclick = function () { sel(t); }; t.onkeydown = function (e) { if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') { e.preventDefault(); sel(tabs[(i + (e.key === 'ArrowRight' ? 1 : tabs.length - 1)) % tabs.length]); } }; });
03. Add and Remove with Smooth Reflow
View TransitionsList
Adding or removing a tag makes the others slide into their new places instead of jumping. Each tag gets a fresh unique view-transition-name when it is created, and new tags fade in through the default cross-fade of their ::view-transition-new.
var ul = document.querySelector('.vt3 ul'), inp = document.getElementById('vt3i'), n = 3;
function vt(fn) { if (document.startViewTransition) document.startViewTransition(fn); else fn(); }
document.querySelector('.vt3 form').onsubmit = function (e) { e.preventDefault(); var v = inp.value.trim(); if (!v) return; vt(function () { var li = document.createElement('li'); li.style.viewTransitionName = 'vt3t' + (n++); li.textContent = v + ' '; var b = document.createElement('button'); b.setAttribute('aria-label', 'Remove ' + v); b.innerHTML = '×'; li.appendChild(b); ul.appendChild(li); }); inp.value = ''; inp.focus(); };
ul.onclick = function (e) { var b = e.target.closest('button'); if (!b) return; vt(function () { b.parentNode.remove(); }); inp.focus(); };
04. Filtering a Grid
View TransitionsFilter
Choosing a filter hides the cards that do not match, and the remaining cards glide into the gaps. The filter buttons are a radio-style group with aria-pressed, hidden cards use the hidden attribute, and the transition is one call around the update.
var bs = document.querySelectorAll('.vt4 .f button'), lis = document.querySelectorAll('.vt4 li');
bs.forEach(function (b) { b.onclick = function () { var f = b.dataset.f; var go = function () { bs.forEach(function (x) { x.setAttribute('aria-pressed', x === b); }); lis.forEach(function (li) { li.hidden = f !== 'all' && li.dataset.t !== f; }); }; if (document.startViewTransition) document.startViewTransition(go); else go(); }; });
05. Theme Switch with Circle Reveal
View TransitionsTheme
The new theme expands as a circle from the toggle, by animating clip-path on ::view-transition-new(vt5), the snapshot of the panel in its new state. The old and new snapshots are separate layers, so the circle reveals the real new state, text and all.
var w = document.querySelector('.vt5'), b = w.querySelector('button');
b.onclick = function () { var go = function () { var on = w.classList.toggle('dark'); b.setAttribute('aria-checked', on); }; if (document.startViewTransition) document.startViewTransition(go); else go(); };
06. Thumbnail to Lightbox
View TransitionsGallery
Opening a thumbnail morphs it into a large view and closing morphs it back, because the thumbnail and the large image share a view-transition-name for the length of the transition. The large view is a dialog, so focus and Escape are handled natively.
Changing a quantity slides the old number up and out while the new one slides up from below, by customising ::view-transition-old and ::view-transition-new for one named element. It shows that view transitions are not limited to cross-fades.
var o = document.querySelector('.vt7 output'), bs = document.querySelectorAll('.vt7 button');
function set(d) { var go = function () { o.value = Math.max(0, +o.value + d); }; if (document.startViewTransition) document.startViewTransition(go); else go(); }
bs[0].onclick = function () { set(-1); }; bs[1].onclick = function () { set(1); };
About these CSS View Transitions
How it works
document.startViewTransition(update) captures a picture of the page, runs your update, captures the new state and animates between them. Elements with a view-transition-name are animated individually from their old box to their new one.
Names must be unique
Each view-transition-name may appear only once on the page at a time. Give list items their own names, and move a shared name from one element to another during the update to morph between them.
Style the pseudo-elements
The animation lives on ::view-transition-group(), ::view-transition-old() and ::view-transition-new(). Override their animations to slide, clip or scale instead of the default cross-fade.
Progressive by design
Check for document.startViewTransition and call the update directly when it is missing. The page works the same; it just does not animate.
Frequently asked questions
Which browsers support view transitions?
Same-document transitions work in Chromium browsers, Safari 18 and Firefox 144 or later.
Do view transitions work between pages?
Yes, with @view-transition { navigation: auto; } for same-origin navigations in supporting browsers.
What about reduced motion?
Each demo turns the transition animations off under prefers-reduced-motion; the updates still happen.