Welcome back, animation explorer! In our previous chapters, you’ve mastered the fundamentals of View Transitions and started to appreciate the power of scoping them to specific parts of your document. We’ve seen how element.startViewTransition() gives you fine-grained control over local animations.
In this chapter, we’re going to unlock a truly exciting capability: running multiple, independent Scoped View Transitions simultaneously on different parts of your webpage! Imagine a dashboard where several widgets can animate their state changes without interfering with each other, or a product page where a gallery image transitions while a related product card also updates with its own smooth animation. This is the power of concurrent scoped transitions, and it’s a game-changer for creating dynamic, responsive user interfaces.
We’ll dive into how the magic of scoping allows these independent animations to coexist gracefully, learn the specific techniques to implement them, and build a hands-on example that brings this concept to life. Get ready to level up your UI animation skills!
Prerequisites
Before we jump in, make sure you’re comfortable with:
- The basics of the View Transition API (
document.startViewTransition()). - The core concepts of Scoped View Transitions and how to initiate them using
element.startViewTransition()(covered in Chapter 6). - Defining
view-transition-namefor elements. - Basic CSS animations and pseudo-elements.
Remember, Scoped View Transitions are an exciting, still-evolving experimental feature as of 2025-12-05. You’ll likely need to enable experimental web platform features in your browser (e.g., via chrome://flags in Chrome) to follow along. We’ll assume you’re using a Chromium-based browser for these examples. You can track its progress and read up on the latest status at the WICG Scoped Transitions GitHub repo and the Chrome Developers blog.
The Magic of Concurrency: How Scoped Transitions Play Nicely
At the heart of concurrent scoped transitions is the fundamental difference between document-scoped and element-scoped transitions.
Document-Scoped Transitions: One at a Time, Please!
When you use document.startViewTransition(), you’re telling the browser to take a snapshot of the entire document, make a change, take another snapshot, and then animate between those two global snapshots. Because there’s only one “document,” only one such transition can be active at any given moment. If you try to start a second document-scoped transition while one is already running, the first one will be immediately canceled. It’s like having a single spotlight on a stage – only one act can be highlighted at a time.
Element-Scoped Transitions: Many Spotlights, Many Acts!
element.startViewTransition() changes this paradigm entirely. Instead of the entire document, this method instructs the browser to:
- Take a snapshot of the specific element you called it on, and its descendants.
- Apply your DOM changes within that element.
- Take another snapshot of that same element and its descendants.
- Animate the transition only within that element’s boundary.
Think of it like this: your webpage is a large theater, but now each individual “card” or “widget” on that page can have its own mini-stage with its own spotlight. When you call card1.startViewTransition(), only card1 and its children are involved in that specific transition. When you call card2.startViewTransition(), card2 and its children get their own independent transition. These two transitions are completely oblivious to each other, allowing them to run simultaneously without conflict.
Key takeaway: The ::view-transition pseudo-elements (like ::view-transition-group, ::view-transition-old, ::view-transition-new) are created within the shadow root of the element that initiated the transition. This containment is what enables true concurrency.
Step-by-Step Implementation: Building a Concurrent Dashboard
Let’s build a simple page with two independent “card” components. Each card will have a button that, when clicked, toggles some content within that card using a Scoped View Transition. Crucially, we’ll be able to activate both transitions at the same time!
1. Setting Up Our HTML Structure
First, let’s create a basic index.html file. We’ll have a container for our cards and two distinct card elements.
Create a file named index.html and add the following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Concurrent Scoped View Transitions</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Concurrent Scoped View Transitions Example</h1>
<div class="card-container">
<!-- Card 1 -->
<div id="card1" class="card">
<h2 class="card-title">Card 1: Project Alpha</h2>
<p class="card-content">This is the initial content for Project Alpha. Click the button to see more details.</p>
<button>Toggle Details</button>
</div>
<!-- Card 2 -->
<div id="card2" class="card">
<h2 class="card-title">Card 2: User Beta Testing</h2>
<p class="card-content">Initial update: 15 new users joined today. Click to see detailed statistics.</p>
<button>Toggle Stats</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
Explanation:
- We have a
div.card-containerto hold our cards. - Two
div.cardelements, each with a uniqueid(card1andcard2), a title, some content, and a button. - Notice the
card-titleandcard-contentclasses. These will be important for identifying the elements we want to transition within each card’s scope.
2. Basic Styling with style.css
Now, let’s add some minimal styling to make our cards look decent and to define the state changes. Create style.css in the same directory.
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
background-color: #f0f2f5;
margin: 0;
padding: 20px;
color: #333;
}
h1 {
color: #2c3e50;
margin-bottom: 40px;
}
.card-container {
display: flex;
gap: 30px; /* Space between cards */
flex-wrap: wrap;
justify-content: center;
}
.card {
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
padding: 25px;
width: 350px;
text-align: center;
transition: transform 0.2s ease-in-out; /* For a subtle hover effect */
display: flex; /* Flexbox for internal layout */
flex-direction: column;
justify-content: space-between;
min-height: 200px;
box-sizing: border-box; /* Include padding in width/height */
}
.card:hover {
transform: translateY(-5px);
}
.card h2 {
color: #34495e;
margin-top: 0;
margin-bottom: 15px;
font-size: 1.6em;
}
.card p {
color: #555;
line-height: 1.6;
margin-bottom: 20px;
font-size: 1.05em;
}
/* State change for card content */
.card.active .card-content {
font-weight: bold;
color: #27ae60; /* A different color when active */
transition: all 0.3s ease; /* Smooth transition for non-VT elements */
}
/* Specific content for active state (will be toggled by JS) */
#card1.active .card-content::after {
content: " Detailed report available now!";
display: block;
margin-top: 10px;
font-size: 0.9em;
color: #2ecc71;
}
#card2.active .card-content::after {
content: " Top 3 regions: North (30%), East (25%), West (20%).";
display: block;
margin-top: 10px;
font-size: 0.9em;
color: #2ecc71;
}
button {
background-color: #007bff;
color: white;
border: none;
padding: 12px 25px;
border-radius: 8px;
cursor: pointer;
font-size: 1.1em;
font-weight: 600;
transition: background-color 0.3s ease, transform 0.1s ease;
align-self: center; /* Center the button within the flex column */
margin-top: auto; /* Push button to the bottom */
}
button:hover {
background-color: #0056b3;
transform: translateY(-1px);
}
button:active {
transform: translateY(0);
}
Explanation:
- Standard styling for the body, heading, and card elements.
- The
.card.active .card-contentrule is crucial. When theactiveclass is added to a card, its.card-contentparagraph will change style and new content will be added via::afterpseudo-element. This state change is what we’ll animate.
3. JavaScript for Toggling State and Initiating Scoped Transitions
Now for the fun part! We’ll add JavaScript to handle button clicks, toggle the active class, and most importantly, use element.startViewTransition() to wrap these state changes. Create script.js in the same directory.
document.addEventListener('DOMContentLoaded', () => {
const card1 = document.getElementById('card1');
const card2 = document.getElementById('card2');
// Function to handle a card's state change
function toggleCardState(cardElement) {
// Feature detection for Scoped View Transitions
if (!cardElement.startViewTransition) {
console.warn("Scoped View Transitions not supported. Toggling class directly.");
cardElement.classList.toggle('active');
return;
}
// Start the scoped view transition for THIS specific card
cardElement.startViewTransition(() => {
// The DOM update happens inside this callback
cardElement.classList.toggle('active');
});
}
// Attach event listeners to the buttons within each card
card1.querySelector('button').addEventListener('click', () => {
toggleCardState(card1);
});
card2.querySelector('button').addEventListener('click', () => {
toggleCardState(card2);
});
});
Explanation:
- We get references to
card1andcard2. toggleCardState(cardElement)is a reusable function. It first checks ifcardElement.startViewTransitionis available (for browser compatibility).- If supported, it calls
cardElement.startViewTransition(), passing a callback function. Inside this callback, we simply toggle theactiveclass on thecardElement. - Event listeners are attached to each card’s button, calling
toggleCardStatewith the respective card element.
Try it out!
Open index.html in your browser. If Scoped View Transitions are not enabled, you’ll see the cards’ content change instantly. If they are enabled, you’ll still see an instant change because we haven’t added any transition styles yet. But crucially, you should be able to click both buttons quickly and see their content change independently.
4. Adding Our Scoped View Transition Styles
Now, let’s add the CSS to make our transitions beautiful. We’ll animate the card-content element within each card.
Add the following CSS to your style.css file, preferably at the bottom:
/* --- Scoped View Transition Styles --- */
/* Give the content within each card a view-transition-name */
/* This name is local to each card's scope! */
.card-content {
view-transition-name: card-content-text;
}
/* Common animation for the old and new content */
::view-transition-old(card-content-text),
::view-transition-new(card-content-text) {
/* Ensure the pseudo-elements fill their space */
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover; /* Important for image-like content */
}
/* Animation for the outgoing (old) content */
::view-transition-old(card-content-text) {
animation: fade-out 0.25s ease-out forwards;
}
/* Animation for the incoming (new) content */
::view-transition-new(card-content-text) {
animation: fade-in 0.25s ease-in forwards;
}
/* Keyframe animations */
@keyframes fade-out {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(-10px); }
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
Explanation:
.card-content { view-transition-name: card-content-text; }: This is the magic. We assign aview-transition-nameto the.card-contentparagraph within each card. BecausestartViewTransitionis called on the card element itself, thisview-transition-nameis effectively scoped to that card. This meanscard1can have acard-content-textandcard2can also have acard-content-textwithout conflict.::view-transition-old(card-content-text), ::view-transition-new(card-content-text): These selectors target the snapshots of ourcard-contentelements. We applyposition: absolute,top: 0,left: 0,width: 100%,height: 100%to ensure they overlay correctly within their parent::view-transition-group.- We define separate
fade-outandfade-inanimations for the old and new content, making the transition smooth. Theforwardskeyword ensures the animation stays at its end state.
Observe the power!
Refresh index.html. Now, click the “Toggle Details” button for Card 1. You should see its content gracefully fade out and in. Then, click the “Toggle Stats” button for Card 2. It will perform its own, independent fade transition.
The real test: Click the button for Card 1, and while it’s still animating, click the button for Card 2. You will observe both cards animating concurrently, completely independent of each other! This is the core benefit of Scoped View Transitions.
Rhetorical Question for the User:
- What would happen if you used
document.startViewTransition()for these animations instead ofelement.startViewTransition()? (Answer: Only one would run at a time; the second click would cancel the first animation.)
Mini-Challenge: Add a Third Concurrent Card
You’ve seen two cards animate independently. Your challenge is to add a third card to our dashboard, give it a unique ID, and make its content toggle with its own independent Scoped View Transition.
Challenge:
- Add a new
divwithid="card3"and classcardto yourindex.htmlwithin thecard-container. Give it a unique title and initial content. - Add a specific
::aftercontent rule forcard3.active .card-contentin yourstyle.css(similar tocard1andcard2). - In
script.js, get a reference tocard3and attach an event listener to its button, callingtoggleCardState(card3).
Hint: You don’t need to change any of the existing CSS for view-transition-name or the animation keyframes. The beauty of scoping is that the .card-content { view-transition-name: card-content-text; } rule will apply to all cards, and each card’s startViewTransition call will create its own local transition context for that card-content-text element.
What to Observe/Learn:
After implementing this, confirm that all three cards can animate their content simultaneously without any conflicts. This reinforces the concept that view-transition-name is effectively local when used within a scoped transition.
Common Pitfalls & Troubleshooting for Concurrent Scoped Transitions
Even with the power of scoping, you might run into a few snags. Here are some common pitfalls and how to navigate them:
Forgetting Browser Feature Flags:
- Pitfall: Your transitions aren’t working at all, or
element.startViewTransitionisundefined. - Troubleshooting: As of 2025-12-05, Scoped View Transitions are still an experimental feature. Ensure you’ve enabled “Experimental Web Platform features” in your browser (e.g.,
chrome://flagsin Chrome, then search for “experimental web platform features” and enable it). Restart your browser after changing flags. - Best Practice: Always include feature detection like
if (!cardElement.startViewTransition)in your production code and provide a fallback.
- Pitfall: Your transitions aren’t working at all, or
Incorrect
view-transition-nameAssignment:- Pitfall: Elements within your scoped transition aren’t animating, or they’re just appearing/disappearing instantly.
- Troubleshooting: Double-check that the element you intend to animate has a
view-transition-nameCSS property applied to it. For example, if you want to animatecard-content, ensure.card-content { view-transition-name: card-content-text; }is present and correctly spelled. Remember,view-transition-nameneeds to be unique within the scope of a single transition. For concurrent scoped transitions, the sameview-transition-namecan be used across different scopes (as shown in our example withcard-content-text).
Issues with
position: absoluteon::view-transition-old/::view-transition-new:- Pitfall: Your transitioning elements appear in the wrong place or don’t fill their expected area during the animation.
- Troubleshooting: The
::view-transition-old()and::view-transition-new()pseudo-elements are often positioned absolutely within their parent::view-transition-group(). If the parent::view-transition-group()doesn’t have apositioncontext (likerelative,absolute,fixed, orsticky), the absolutely positioned elements might position themselves relative to the nearest positioned ancestor (or even the initial containing block). While the browser usually handles this well for::view-transition-groupitself, ensure your specific::view-transition-old/::view-transition-newstyling (liketop: 0,left: 0,width: 100%,height: 100%) correctly places them within their temporary parent.
Complex DOM Changes within the Callback:
- Pitfall: Animations are janky or unexpected, especially if you’re making many changes or adding/removing elements during the
startViewTransitioncallback. - Troubleshooting: Keep the DOM changes within the
startViewTransitioncallback as minimal and focused as possible. The browser needs to take two snapshots, and excessive changes can make this process less efficient. If you’re encountering performance issues, consider simplifying your DOM updates or optimizing your CSS.
- Pitfall: Animations are janky or unexpected, especially if you’re making many changes or adding/removing elements during the
Summary
Congratulations! You’ve just mastered one of the most powerful features of Scoped View Transitions: concurrency.
Here’s a quick recap of what we’ve covered:
- Concurrency Enabled by Scoping: Unlike document-scoped transitions which are global and exclusive,
element.startViewTransition()allows multiple, independent transitions to run simultaneously on different DOM subtrees. - Independent Contexts: Each call to
element.startViewTransition()creates its own isolated transition context. The::view-transitionpseudo-elements and theview-transition-namevalues within one scope do not interfere with those in another. - Practical Implementation: We built a mini-dashboard where two (and then three!) cards could update their content with smooth animations, all happening concurrently.
- Key API: The
element.startViewTransition()method is your gateway to these powerful local animations. - Troubleshooting: We discussed common issues like feature flag requirements, correct
view-transition-nameusage, and styling considerations.
You now have the tools to create incredibly dynamic and engaging user interfaces where different components can animate their states independently, leading to a much richer and more responsive user experience.
What’s Next?
In the next chapter, we’ll explore even more advanced techniques, such as animating elements that are added or removed from the DOM within a scoped transition, and diving deeper into customizing entry and exit animations. Get ready to add even more polish to your web creations!