TL;DR: The View Transitions API brings buttery-smooth page transitions directly to the browser - no framework, no JavaScript library needed. Just CSS and a few lines of JS. Here's how it works and why you should start using it today.
🤔 What Are View Transitions Anyway?
You know the feeling. You click a link and - bam - the page jumps. No transition, no fading, just a hard cut. Feels like 2005. Native apps do this better: screens glide into each other, images morph from one view to the next.
That's exactly the problem the View Transitions API solves. It gives you the ability to create animated transitions between different states of your page - natively in the browser. No Angular, no React, no Framer Motion. Just the platform.
😩 The Problem: Page Navigation Feels Broken
Let's be honest: Multi-Page Applications (MPAs) always feel a bit... janky compared to native apps. Every page change is a complete rebuild. The browser loads HTML, parses CSS, re-renders everything. The result? A white flash, a jump, a break in the flow.
Single-Page Applications (SPAs) solved this with client-side routing - but at a high cost: JavaScript bundles, framework dependencies, complexity. The View Transitions API offers a middle ground.
🚀 Same-Document Transitions with document.startViewTransition()
The easiest entry point: transitions within a single page. Think tabs or an image gallery. Instead of just swapping content, you can animate the change.
function switchTab(newContent) {
// Check if the API is available
if (!document.startViewTransition) {
updateDOM(newContent);
return;
}
// Start a view transition
document.startViewTransition(() => {
updateDOM(newContent);
});
}
function updateDOM(content) {
document.querySelector('.tab-content').textContent = content;
}That's it. Seriously. The browser now automatically creates a crossfade between the old and new state. You don't have to do anything else. The browser takes snapshots of both states and animates between them.
🌍 Cross-Document Transitions with @view-transition
Now it gets really exciting. Since Chrome 126, you can animate transitions between different pages - without any JavaScript! All you need is a CSS rule:
@view-transition {
navigation: auto;
}Yes, that's all. Add this to your stylesheet and every page navigation within the same origin gets an automatic smooth crossfade. Not a single line of JavaScript required.
Important: Both pages (the old and new one) need this rule in their CSS. Best practice is to put it in your global stylesheet.
🎯 view-transition-name: Target Specific Elements
The real wow effect comes when you link individual elements across pages. Imagine a product list: you click on a thumbnail and it morphs into the large image on the detail page.
/* On the list page */
.product-card img {
view-transition-name: product-hero;
}
/* On the detail page */
.product-detail img {
view-transition-name: product-hero;
}Same name = the browser knows these elements belong together. It automatically calculates position, size, and animates between them. The result looks like a native app.
Important: Each view-transition-name must be unique on a page. Two elements with the same name on the same page? That breaks the transition.
🎨 ::view-transition Pseudo-Elements for Custom Animations
During a transition, the browser generates a pseudo-element tree that you can style with CSS:
/* Customize the entire transition */
::view-transition-old(root) {
animation-duration: 0.3s;
}
::view-transition-new(root) {
animation-duration: 0.3s;
}
/* Animate specific elements differently */
::view-transition-old(product-hero) {
animation: slide-out 0.4s ease-in;
}
::view-transition-new(product-hero) {
animation: slide-in 0.4s ease-out;
}The pseudo-elements are:
::view-transition- the root container::view-transition-group(name)- groups old and new snapshots::view-transition-image-pair(name)- contains old and new views::view-transition-old(name)- snapshot of the old state::view-transition-new(name)- snapshot of the new state
💡 Practical Example: Image Gallery with View Transitions
Here's a complete example for an image gallery with smooth transitions:
/* Basic setup */
@view-transition {
navigation: auto;
}
/* Thumbnail on the overview page */
.gallery-grid img {
view-transition-name: var(--transition-name);
object-fit: cover;
border-radius: 8px;
}
/* Large image on the detail page */
.gallery-detail img {
view-transition-name: var(--transition-name);
width: 100%;
max-height: 80vh;
object-fit: contain;
}// Dynamically assign unique names
document.querySelectorAll('.gallery-grid img').forEach((img, i) => {
img.style.setProperty('--transition-name', `gallery-img-${i}`);
});Each image gets its own transition name. When you click on an image, it seamlessly morphs into the detail view.
🔀 Tab Switching with View Transitions
View Transitions also make sense within a single page. Here's a tab example:
const tabs = document.querySelectorAll('.tab-button');
const content = document.querySelector('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', async () => {
const newContent = await fetchTabContent(tab.dataset.tab);
if (!document.startViewTransition) {
content.textContent = newContent;
return;
}
const transition = document.startViewTransition(() => {
content.textContent = newContent;
});
// Optional: wait for the transition to finish
await transition.finished;
console.log('Transition complete!');
});
});.tab-content {
view-transition-name: tab-panel;
}
::view-transition-old(tab-panel) {
animation: fade-and-slide-out 0.25s ease-in;
}
::view-transition-new(tab-panel) {
animation: fade-and-slide-in 0.25s ease-out;
}
@keyframes fade-and-slide-out {
to {
opacity: 0;
transform: translateX(-20px);
}
}
@keyframes fade-and-slide-in {
from {
opacity: 0;
transform: translateX(20px);
}
}✨ Custom Effects with @keyframes
You can completely replace the default crossfade animation. How about a slide effect for page navigation?
/* Slide transition for page changes */
@keyframes slide-to-left {
to {
transform: translateX(-100%);
}
}
@keyframes slide-from-right {
from {
transform: translateX(100%);
}
}
::view-transition-old(root) {
animation: slide-to-left 0.4s ease-in-out;
}
::view-transition-new(root) {
animation: slide-from-right 0.4s ease-in-out;
}
/* Or a cool scale effect */
@keyframes scale-down {
to {
transform: scale(0.9);
opacity: 0;
}
}
@keyframes scale-up {
from {
transform: scale(1.1);
opacity: 0;
}
}
::view-transition-old(root) {
animation: scale-down 0.3s ease-in;
}
::view-transition-new(root) {
animation: scale-up 0.3s ease-out;
}You can even respect prefers-reduced-motion:
@media (prefers-reduced-motion: reduce) {
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.01s;
}
}🛡️ Progressive Enhancement: Feature Detection
View Transitions are the perfect example of progressive enhancement. Browsers that don't support it? They just get the normal page change - no errors, no fallback code needed.
// JavaScript Feature Detection
if (document.startViewTransition) {
// Use View Transitions
document.startViewTransition(() => updateContent());
} else {
// Fallback: just update
updateContent();
}
// Or as a utility function
function withTransition(callback) {
if (!document.startViewTransition) {
callback();
return Promise.resolve();
}
return document.startViewTransition(callback).finished;
}/* CSS Feature Detection */
@supports (view-transition-name: test) {
.animated-element {
view-transition-name: my-element;
}
}That's the beauty of it: there's no breaking change. Either the browser supports it - and it looks great. Or it doesn't - and everything works as before.
🌐 Browser Support
Here's the current status (March 2026):
| Browser | Same-Document | Cross-Document |
|---|---|---|
| Chrome / Edge | From 111 | From 126 |
| Safari | From 18 | From 18 |
| Firefox | From 133 | From 133 (behind flag) |
| Opera | From 97 | From 112 |
Same-document transitions are now supported by all modern browsers. Cross-document transitions are stable in Chrome/Edge and Safari, with Firefox catching up.
⚔️ View Transitions vs. Framework Solutions
How do View Transitions stack up against framework animations?
| Criteria | View Transitions API | Framework Solutions |
|---|---|---|
| Bundle Size | 0 KB (native) | 5-50 KB (Framer Motion, Angular Animations) |
| Setup | Minimal (CSS + little JS) | Framework-specific, often complex |
| Cross-Page | Natively supported | Only within the SPA |
| Performance | Browser-optimized, compositor thread | JavaScript-based, main thread |
| Learning Curve | Low (CSS + simple API) | High (framework-specific) |
| Browser Support | Growing, not yet 100% | Everywhere (polyfills included) |
| Flexibility | Good for standard transitions | Very high, complex animations |
Sure, Framer Motion or Angular Animations offer more control over complex animation sequences. But for the vast majority of page transitions, the View Transitions API is the better choice: less code, better performance, and it even works without JavaScript for cross-document transitions.
💡 Conclusion
The View Transitions API is a genuine game-changer. It closes the gap between web and native apps - with minimal complexity. A few lines of CSS, optionally a bit of JavaScript, and your site suddenly feels like a native app.
The best part: it's progressive enhancement in its purest form. You add it, and those who can use it get a better experience. Everyone else notices no difference.
So: try it out. Add @view-transition { navigation: auto; } to your stylesheet and see what happens. You'll be surprised how much a simple crossfade can do.
Discover more articles
View Transitions API: Smooth Page Transitions ohne Framework ✨
TL;DR: Die View Transitions API bringt butterweiche Seitenuebergaenge direkt in den Browser - ganz ohne Framework, ganz ohne JavaScript-Bibliothek. Einfach CSS und ein paar Zeilen JS. Hier erfaehrst du, wie das funktioniert und warum du es sofort ausprobieren solltest. 🤔 Was sind View Transitions ueberhaupt? Kennst du das? Du klickst
CSS :has() – The Parent Selector Everyone’s Been Waiting For 🎯
TL;DR: CSS :has() is the long-awaited parent selector – you can finally style parent elements based on their children. No more JavaScript hacks, no workarounds, just pure CSS. And yes, it works in all modern browsers. 🤔 Why CSS Never Had a Parent Selector If you've been writing CSS
Container Queries in CSS: The End of Media Queries? 📦
Container Queries put an end to viewport chaos! 📦 How to make your components truly responsive – and why Media Queries are not dead yet.
Angular and TailwindCSS: Utility-First CSS Meets Components 🎨
Angular and TailwindCSS are a powerful duo. Here's how to set up Tailwind in your Angular project and use utility-first CSS effectively.
Tailwind CSS vs. Vanilla CSS – When Is Which Worth It? ⚖️
Tailwind CSS or Vanilla CSS? ⚖️ I will show you when each approach is actually worth it – with real examples and honest pros and cons!