ShaharAmir
← Back to Blog
JavaScript4 min read

View Transitions API: Native Page Animations

The browser now handles smooth page transitions natively. No libraries needed — just CSS and a few lines of JavaScript.

S
Shahar Amir

View Transitions API: Native Page Animations

Remember adding libraries just for page transitions? Those days are over. The browser does it natively now.

View Transitions

The Old Way

You needed Framer Motion, React Transition Group, or GSAP:

javascript
12345678910111213141516
// 50KB+ of JavaScript for fade transitions
import { AnimatePresence, motion } from "framer-motion";
function App() {
return (
<AnimatePresence mode="wait">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
{page}
</motion.div>
</AnimatePresence>
);
}

The New Way

One function. Zero dependencies:

javascript
1234
document.startViewTransition(() => {
// Update the DOM however you want
updatePage(newContent);
});

The browser handles the animation automatically.

How It Works

  1. Browser screenshots the current state
  2. Your callback updates the DOM
  3. Browser screenshots the new state
  4. Crossfades between them

javascript
123456789
// Navigation with smooth transition
async function navigate(url) {
const response = await fetch(url);
const html = await response.text();
document.startViewTransition(() => {
document.body.innerHTML = html;
});
}

Custom Animations with CSS

The magic: you control everything with CSS.

css
12345678910111213141516171819202122
/* Slow down the transition */
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.5s;
}
/* Slide instead of fade */
::view-transition-old(root) {
animation: slide-out 0.3s ease-out;
}
::view-transition-new(root) {
animation: slide-in 0.3s ease-out;
}
@keyframes slide-out {
to { transform: translateX(-100%); }
}
@keyframes slide-in {
from { transform: translateX(100%); }
}

Named Transitions

Animate specific elements independently:

css
12345678
/* Give elements a transition name */
.hero-image {
view-transition-name: hero;
}
.page-title {
view-transition-name: title;
}

Now the hero image and title animate separately from the page:

css
1234567891011
/* Hero scales and moves */
::view-transition-old(hero),
::view-transition-new(hero) {
animation-duration: 0.4s;
animation-timing-function: ease-out;
}
/* Title slides up */
::view-transition-new(title) {
animation: slide-up 0.3s ease-out;
}

Live Demo

<style>
  .demo { font-family: system-ui; padding: 20px; }
  .card {
    padding: 20px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border-radius: 12px;
    color: white;
    cursor: pointer;
    transition: transform 0.15s;
  }
  .card:hover { transform: scale(1.02); }
  .card h2 { margin: 0 0 8px 0; }
  .card p { margin: 0; opacity: 0.9; }
  .expanded {
    background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
  }
  .status {
    margin-top: 12px;
    padding: 8px;
    background: #1a1a2e;
    border-radius: 6px;
    font-size: 13px;
    color: #a0a0a0;
  }
</style>

<div class="demo">
  <div class="card" id="card" onclick="toggleCard()">
    <h2 id="title">Click Me</h2>
    <p id="content">Watch the native transition</p>
  </div>
  <div class="status" id="status">Click the card to trigger a View Transition</div>
</div>

<script>
  let expanded = false;
  
  function toggleCard() {
    if (!document.startViewTransition) {
      updateCard();
      document.getElementById("status").textContent = "View Transitions not supported - instant update";
      return;
    }
    
    document.getElementById("status").textContent = "Transitioning...";
    
    document.startViewTransition(() => {
      updateCard();
    }).finished.then(() => {
      document.getElementById("status").textContent = expanded 
        ? "Expanded with View Transition!" 
        : "Collapsed with View Transition!";
    });
  }
  
  function updateCard() {
    expanded = !expanded;
    const card = document.getElementById("card");
    const title = document.getElementById("title");
    const content = document.getElementById("content");
    
    card.classList.toggle("expanded", expanded);
    title.textContent = expanded ? "Expanded!" : "Click Me";
    content.textContent = expanded 
      ? "The browser animated this automatically" 
      : "Watch the native transition";
  }
</script>

Framework Integration

Works with any framework. Here is React:

javascript
123456789101112131415
import { useNavigate } from "react-router-dom";
function Link({ to, children }) {
const navigate = useNavigate();
function handleClick(e) {
e.preventDefault();
document.startViewTransition(() => {
navigate(to);
});
}
return <a href={to} onClick={handleClick}>{children}</a>;
}

Browser Support

  • Chrome 111+
  • Edge 111+
  • Safari 18+
  • Firefox: behind flag

Always feature-detect:

javascript
12345
if (document.startViewTransition) {
document.startViewTransition(() => update());
} else {
update(); // Fallback: instant update
}

Key Takeaways

  • Zero dependencies — built into the browser
  • CSS-powered — full control over animations
  • Progressive enhancement — falls back gracefully
  • Works everywhere — vanilla JS, React, Vue, Astro

Page transitions used to require heavy libraries. Now it is one function call. The platform keeps getting better.

---

Ship smoother experiences with less code. That is the web in 2026.

#view-transitions#css#animation#spa#browser-api

Stay Updated 📬

Get the latest tips and tutorials delivered to your inbox. No spam, unsubscribe anytime.