Being lazy with view-transition-old and -new
One of the most important lessons you can learn as a developer is that being lazy is often a good thing. Students sometimes laugh sheepishly at me when I tell them that, but especially if it leads to writing less (or less complicated) code, being lazy is smart.
A short intro to View Transitions
View Transitions let you animate between two DOM states (or even between pages) using CSS keyframes. If you need a more in-depth introduction, have a look at my earlier blog or the second half of my Beyond Tellerrand talk.
The anatomy of a view transition
If you open DevTools in Chrome and inspect a View Transition, you’ll see a structure like this:
::view-transition
::view-transition-group(root)
::view-transition-image-pair(root)
::view-transition-old(root)
::view-transition-new(root)HTMLA quick breakdown of each part:
::view-transition
All named view transitions 1 will end up in this parent pseudo element::view-transition-group
Each named view transition element will get its own view transition group, ‘root’ will be equal to the givenview-transition-name. In the vt-group all transforms are automatically calculated.::view-transition-image-pair
This element is set toisolation: isolateso the default blend modes animation of the old and new view transitions don’t mix with anything but eachother.::view-transition-oldand::view-transition-new
These represent, as you hopefully already expected, a snapshot of the old and new state of the named element.
Being lazy with view-transition-old and view-transition-new
The -old and -new state of a view transition can be very useful, it’s easiest to explain with an example of filtering and sorting.
Sorting
If you start a View Transition while changing the DOM order of items (e.g. sorting a list), the browser will automatically animate them to their new position with the help of the calculations in the view-transition-group.
Because the items exist both before and after the DOM change, they will each have a ::view-transition-old and ::view-transition-new state.
Filtering
If you’re filtering items out of a list, the ones that disappear exist in the old state but not in the new one. They will only have a ::view-transition-old state.
If you add items back with the same filter, the opposite is true. They only exist in the new state, so they’ll only have a ::view-transition-new.
This might sound useless at first (it did to me), but with CSS we can check if there is only a view transition old or new state and style based on that.
::view-transition-old(.filter-item):only-child {
animation-name: animate-out;
}
::view-transition-new(.filter-item):only-child {
animation-name: animate-in;
}CSSThis means you can create custom -in and -out animations for items in for example:
See the Pen View transitions – CSS only by Cyd Stumpel (@Sidstumple) on CodePen.
Using it between pages
You can take this one step further and use it to help you animate between overview and detail pages. That’s what I did when building this website for my CSS Day talk. This way you can create smooth transitions between different pages with minimal extra work.
When going from an overview with speakers to a detail page for example, only the speaker that is clicked will have an old and a new state, the other speakers will only have an old state. When going from a detail page back to an overview only the speaker that was active will have both states. It would be even more useful if the :has selector would work on pseudo elements because then we could also adjust the stacking order of the parent group to make sure it is in front of all the other elements.
It doesn’t look like browsers will implement :has on pseudo elements: *
“Pseudo-elements are also not valid selectors within :has() and pseudo-elements are not valid anchors for :has(). This is because many pseudo-elements exist conditionally based on the styling of their ancestors and allowing these to be queried by :has() can introduce cyclic querying.”
– MDN
::view-transition-group(.filter-item):has(::view-transition-old(.filter-item):only-child) {
z-index: 1; /* Won't work 🙁 */
}CSSUsing delays can help you avoid stacking the items on top of each other, or you could give the active item a different view transition class on click with JavaScript and use that to give the group a higher stacking context.
1 When I talk about named view transitions or named elements I mean elements that have a view-transition-name.
Update: An issue has been opened on the CSS Working Group, we might be able to do this in the future 🤩 https://github.com/w3c/csswg-drafts/issues/12630
Last updated: September 18, 2025
Replies 2
@Cyd These posts are great. I remember from your talk that you used `:only-child` with view transition stuff in CSS and it was related to like “incoming only” or “outgoing only” transitions right? That syntax wasn’t intuitive to me, you got a post on that?
…. errrrr wait that’s literally what this is, isn’t it
@chriscoyier hahaha yes this is exactly that, view transitions are a whole new mental model and so abstract, took me a while to get used to, too
Likes 9
Reposts 4
Webmentions are a way to connect with other people who have shared your work.