CSS View Transitions API Guide
Create smooth, native-feeling page transitions with the View Transitions API.
Control animations entirely with CSS.
Table of Contents
1. Introduction to View Transitions
The View Transitions API provides a mechanism for creating animated transitions between different DOM states. It captures snapshots of the page before and after a change, then animates between them.
What are View Transitions?
- A native browser API for animating DOM changes
- Creates smooth transitions without complex animation libraries
- Works with both SPAs (single-page apps) and MPAs (multi-page apps)
- Animations are fully controllable with CSS
Browser Support
Note: View Transitions are progressively enhanced. If the browser doesn't support them, the DOM update still happens - just without the animation.
Same-Document vs Cross-Document Transitions
Same-Document (SPA)
For single-page applications where navigation happens via JavaScript. Use document.startViewTransition().
Cross-Document (MPA)
For traditional multi-page applications with full page navigations. Use @view-transition CSS at-rule.
2. Basic Usage
The simplest way to use View Transitions is with the document.startViewTransition() API. This captures the current state, runs your update callback, then animates to the new state.
The startViewTransition() API
// Basic usage
document.startViewTransition(() => {
// Update the DOM here
updateDOM();
});
// With async/await
document.startViewTransition(async () => {
const data = await fetchNewContent();
container.innerHTML = data;
});Default Crossfade Animation
Without any custom CSS, the View Transitions API applies a default crossfade animation between the old and new states.
// This will create a smooth crossfade by default
document.startViewTransition(() => {
document.querySelector('.content').textContent = 'New content!';
});Handling the Transition Object
const transition = document.startViewTransition(() => {
updateDOM();
});
// Wait for the transition to finish
await transition.finished;
// Wait for the new view to be ready
await transition.ready;
// Skip the transition animation
transition.skipTransition();Important: The callback passed to startViewTransition()should update the DOM synchronously or return a Promise. The API waits for the Promise to resolve before capturing the new state.
3. Customizing Transitions with CSS
The real power of View Transitions comes from CSS customization. The API creates pseudo-elements that you can style with standard CSS animations.
View Transition Pseudo-Elements
::view-transition - Root container for all transitions::view-transition-group(name) - Animates size and position::view-transition-image-pair(name) - Contains old and new images::view-transition-old(name) - Screenshot of the old state::view-transition-new(name) - Live view of the new stateCustomizing the Root Transition
/* Change animation duration */
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.5s;
}
/* Disable the default crossfade */
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}Custom Fade Animation
/* Custom fade-in animation */
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
::view-transition-old(root) {
animation: fade-out 0.3s ease-out forwards;
}
::view-transition-new(root) {
animation: fade-in 0.3s ease-in forwards;
}Slide Transition
@keyframes slide-out-left {
to { transform: translateX(-100%); }
}
@keyframes slide-in-right {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
::view-transition-old(root) {
animation: slide-out-left 0.4s ease-in-out forwards;
}
::view-transition-new(root) {
animation: slide-in-right 0.4s ease-in-out forwards;
}Scale and Fade
@keyframes scale-down-fade {
to {
opacity: 0;
transform: scale(0.9);
}
}
@keyframes scale-up-fade {
from {
opacity: 0;
transform: scale(1.1);
}
to {
opacity: 1;
transform: scale(1);
}
}
::view-transition-old(root) {
animation: scale-down-fade 0.3s ease-out forwards;
}
::view-transition-new(root) {
animation: scale-up-fade 0.3s ease-out forwards;
}4. Named View Transitions
Named view transitions allow you to animate specific elements independently, creating more sophisticated transition effects like hero animations.
The view-transition-name Property
/* Give an element a unique transition name */
.hero-image {
view-transition-name: hero;
}
.page-title {
view-transition-name: title;
}
/* Each name must be unique on the page */Rule: Only one element can have a given view-transition-name at any time. If multiple elements share the same name, the transition will fail.
Hero Image Transition Example
A classic example is animating a thumbnail to a full-size image when navigating to a detail page.
/* On the list page */
.product-thumbnail {
view-transition-name: product-image;
}
/* On the detail page */
.product-hero {
view-transition-name: product-image;
}
/* The API will automatically animate between them! */Styling Named Transitions
/* Style the hero image transition specifically */
::view-transition-group(hero) {
animation-duration: 0.5s;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
/* Keep the image aspect ratio during animation */
::view-transition-old(hero),
::view-transition-new(hero) {
object-fit: cover;
width: 100%;
height: 100%;
}
/* Different animations for different elements */
::view-transition-group(title) {
animation-duration: 0.3s;
}Disabling Transitions for Specific Elements
/* Exclude an element from the transition */
.no-transition {
view-transition-name: none;
}
/* Or use the contain property */
.static-sidebar {
view-transition-name: sidebar;
contain: layout;
}5. Practical Examples
Page Transition Effect
A smooth page transition for SPA navigation with a vertical slide effect.
// JavaScript: Trigger transition on navigation
async function navigateTo(url) {
if (!document.startViewTransition) {
// Fallback for unsupported browsers
window.location.href = url;
return;
}
const transition = document.startViewTransition(async () => {
const response = await fetch(url);
const html = await response.text();
document.querySelector('main').innerHTML = html;
});
await transition.finished;
}/* CSS: Page transition styles */
@keyframes slide-up-out {
to {
opacity: 0;
transform: translateY(-30px);
}
}
@keyframes slide-up-in {
from {
opacity: 0;
transform: translateY(30px);
}
}
::view-transition-old(root) {
animation: slide-up-out 0.25s ease-out forwards;
}
::view-transition-new(root) {
animation: slide-up-in 0.25s ease-out forwards;
}List Item Reordering
Smoothly animate list items when they are reordered.
/* Each list item gets a unique name */
.list-item {
view-transition-name: var(--item-name);
}
/* Use CSS custom properties for unique names */
.list-item:nth-child(1) { --item-name: item-1; view-transition-name: item-1; }
.list-item:nth-child(2) { --item-name: item-2; view-transition-name: item-2; }
.list-item:nth-child(3) { --item-name: item-3; view-transition-name: item-3; }
/* ... and so on */
/* Animate the group (position and size) */
::view-transition-group(item-1),
::view-transition-group(item-2),
::view-transition-group(item-3) {
animation-duration: 0.3s;
animation-timing-function: ease-out;
}// JavaScript: Reorder with transition
function moveItemUp(index) {
document.startViewTransition(() => {
const list = document.querySelector('.list');
const items = Array.from(list.children);
if (index > 0) {
list.insertBefore(items[index], items[index - 1]);
}
});
}Image Gallery Transitions
Click a thumbnail to expand it into a full-screen view with a smooth morph effect.
/* Thumbnail grid */
.gallery-thumb {
view-transition-name: gallery-image;
cursor: pointer;
}
/* Full-size view */
.gallery-full {
view-transition-name: gallery-image;
position: fixed;
inset: 0;
object-fit: contain;
background: rgba(0, 0, 0, 0.9);
}
/* Customize the morph animation */
::view-transition-group(gallery-image) {
animation-duration: 0.4s;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
/* Keep the background from animating */
.gallery-backdrop {
view-transition-name: none;
}Tab Switching Animation
Animate between tab panels with directional awareness.
// JavaScript: Tab switching with direction detection
let currentIndex = 0;
function switchTab(newIndex) {
const direction = newIndex > currentIndex ? 'forward' : 'backward';
document.documentElement.dataset.direction = direction;
document.startViewTransition(() => {
// Hide current panel, show new panel
document.querySelectorAll('.tab-panel').forEach((panel, i) => {
panel.hidden = i !== newIndex;
});
currentIndex = newIndex;
});
}/* CSS: Direction-aware animations */
.tab-panel {
view-transition-name: tab-content;
}
/* Forward navigation (left to right) */
[data-direction="forward"]::view-transition-old(tab-content) {
animation: slide-out-left 0.3s ease-out;
}
[data-direction="forward"]::view-transition-new(tab-content) {
animation: slide-in-right 0.3s ease-out;
}
/* Backward navigation (right to left) */
[data-direction="backward"]::view-transition-old(tab-content) {
animation: slide-out-right 0.3s ease-out;
}
[data-direction="backward"]::view-transition-new(tab-content) {
animation: slide-in-left 0.3s ease-out;
}
@keyframes slide-out-left { to { transform: translateX(-100%); opacity: 0; } }
@keyframes slide-in-right { from { transform: translateX(100%); opacity: 0; } }
@keyframes slide-out-right { to { transform: translateX(100%); opacity: 0; } }
@keyframes slide-in-left { from { transform: translateX(-100%); opacity: 0; } }6. Cross-Document Transitions (MPA)
For traditional multi-page applications, you can enable view transitions between full page navigations using CSS alone - no JavaScript required!
The @view-transition At-Rule
/* Enable cross-document view transitions */
@view-transition {
navigation: auto;
}
/* This goes in your CSS and applies to all same-origin navigations */Requirements: Cross-document transitions only work for same-origin navigations (same domain). They won't work for external links or cross-origin redirects.
Styling MPA Transitions
/* Enable transitions */
@view-transition {
navigation: auto;
}
/* Customize the page transition */
::view-transition-old(root) {
animation: fade-scale-out 0.4s ease-out forwards;
}
::view-transition-new(root) {
animation: fade-scale-in 0.4s ease-out forwards;
}
@keyframes fade-scale-out {
to {
opacity: 0;
transform: scale(0.95);
}
}
@keyframes fade-scale-in {
from {
opacity: 0;
transform: scale(1.05);
}
}Shared Elements Across Pages
The same view-transition-name technique works across pages. If an element on page A and an element on page B share the same name, they will animate between each other.
/* page-list.html */
.product-card img {
view-transition-name: product-hero;
}
/* page-detail.html */
.product-detail img {
view-transition-name: product-hero;
}
/* The image will smoothly morph from card to detail view */Progressive Enhancement
/* Feature detection with @supports */
@supports (view-transition-name: test) {
@view-transition {
navigation: auto;
}
.animate-on-transition {
view-transition-name: my-element;
}
}
/* Browsers without support simply skip these rules */Best Practices
- Keep transition durations short (200-500ms) for perceived performance
- Use
prefers-reduced-motionmedia query to respect user preferences - Test transitions on slower devices to ensure smooth performance
- Provide fallbacks for browsers that don't support View Transitions
- Avoid animating too many elements at once to prevent jank
Respecting Reduced Motion
/* Disable animations for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
}
/* Or reduce duration significantly */
::view-transition-group(*) {
animation-duration: 0.01s;
}
}Quick Reference
CSS Properties
CSS At-Rules
Pseudo-Elements
JavaScript API
Explore more CSS tools
CSS Grid Generator
Create grid layouts visually
CSS Flexbox Generator
Create flex layouts visually
CSS Gradient Generator
Create custom gradients
CSS Text Gradient
Gradient text effects
CSS Gradients Collection
275+ ready-to-use gradients
CSS Box Shadow Generator
Design multi-layer shadows
CSS Selection Generator
Style text highlight colors
CSS Scrollbar Generator
Style custom scrollbars
CSS Blend Mode Generator
Mix colors with blend modes
CSS Cursor Generator
Preview all cursor styles
CSS Loader Generator
Create loading animations
CSS Button Generator
Design buttons with hover effects
CSS Glow Generator
Create neon glow effects
Fluid Typography
Scale text across screen sizes
