View Transition Name Collisions and Scoping
A view-transition-name must be unique among rendered elements when a transition captures. Two elements claiming the same name is not resolved by picking one — the specification treats it as an error and the transition is skipped entirely. Because nothing is logged, the symptom is a navigation that cuts, which looks exactly like a transition that was never added. This guide covers why collisions happen, how to avoid them structurally, and how to clear names reliably. It sits under cross-route element morphing.
When to use this approach
- A morph that works in a prototype and cuts in production. The prototype had one card; production has twelve.
- A transition that works on some routes and not others. A shared component is contributing a name on the routes where it appears twice.
- A design system shipping components that use transitions. Names are effectively global, so components need a namespacing convention before they collide.
- A transition that breaks on the second navigation. A name left behind from the first is claiming the slot.
Implementation
1. Never assign a repeated name from a stylesheet
/* wrong — matches every card */
.card { view-transition-name: hero; }
A name in a stylesheet is fine for genuinely singular elements — a page header, a single hero — and wrong for anything that repeats. The test is whether the selector can ever match twice on one page.
2. Assign at interaction time to the specific element
function openDetail(card) {
card.style.viewTransitionName = 'article-hero';
const t = document.startViewTransition(() => commit(card.dataset.route));
t.finished.finally(() => {
card.style.viewTransitionName = '';
});
}
finally rather than then matters: a transition that is skipped or aborted still needs its name cleared, and a then handler will not run in those cases.
3. Give the destination the matching name
/* the detail page's hero is singular, so a stylesheet rule is correct here */
.detail-hero { view-transition-name: article-hero; }
The pairing is by name only. The element on the destination can be a different tag with a different parent and a different size; what it cannot be is absent, or duplicated.
4. Namespace names per component
.article-card__media { /* name assigned at interaction: article-hero */ }
.gallery-item__media { /* name assigned at interaction: gallery-hero */ }
5. Clear names on unmount as well
In a component framework, an element that is removed while a transition is pending can leave a name registered against nothing. Clearing the name in the component’s teardown, in addition to on finished, covers the case.
Verification
Trigger the morph twice in a row. A stale name from the first navigation breaks the second, and testing a single navigation will not reveal it.
Then trigger it from a list with several items and confirm the morph starts from the item you activated rather than from a fixed one. A morph that always begins from the first card is a stylesheet-assigned name.
Inspect the pseudo-element tree during the transition. A group per intended name means the pairing worked; an empty tree means the transition was skipped, which on a page with repeated components is almost always a collision.
Finally, check a route where two components that both use transitions appear together. This is the case a namespacing convention exists for, and the one that is never exercised until it ships.
Edge cases and gotchas
A name on an element inside a repeated component. The collision is the same whether the name is on the component root or on something within it.
Names surviving a route change. If the element carrying the name is not unmounted — a persistent shell element, for instance — the name persists with it into the next transition.
Two elements where only one is rendered. An element with display: none is not rendered and does not participate, so a hidden duplicate is not a collision. An element with opacity: 0 is rendered and does collide.
Names assigned by a framework’s style prop. These are inline styles, so they win the cascade and are easy to leave behind on re-render. Clearing them explicitly is more reliable than clearing a class.
Custom-property-driven names. Deriving a name from a data attribute is a neat pattern and makes collisions harder to spot, since the duplication is in the data rather than in the CSS.
Browser-specific notes
The uniqueness requirement and the skip behaviour are specified, so all three engines behave the same way: no warning, no partial transition, just a cut.
Chromium’s DevTools show the generated pseudo-element tree during a transition, which is the fastest way to confirm whether one was created at all. Slowing the transition down in the Animations drawer makes the tree easier to inspect.
Safari behaves identically and offers no equivalent inspection, so confirming a collision there means reasoning from the markup or temporarily lengthening the transition.
Firefox, with same-document transitions behind a flag in current stable builds, takes the fallback path by default — which means a collision can go unnoticed there because nothing was going to animate anyway.
Frequently Asked Questions
Why does the specification skip rather than pick one?
Because any choice would be arbitrary and the result would be unpredictable across engines and across page states. Skipping is deterministic and — importantly — leaves the navigation correct, since the DOM update still happens. It is a poor developer experience and a defensible design decision.
Can I use view-transition-class instead?
For styling several groups together, yes — that is exactly what it is for, and it does not affect pairing. What it does not do is remove the uniqueness requirement on names: each element still needs its own name, and the class is how you apply shared animation rules to the resulting groups.
Is there a lint rule for this?
Nothing standard, but the check is simple enough to write: flag any CSS rule that sets view-transition-name with a selector that is not an id. That catches the class-based case, which is the overwhelming majority, and it is a rule worth adding to a design system’s own linting.
What happens if the destination has no matching name?
The element on the origin side has no pair, so it does not morph — it animates on its own as an exit, and the destination element animates as an entry. That is a legitimate effect and occasionally what you want; when it is not, it is usually a typo in one of the two names.
How do I audit an existing codebase for this?
Search for view-transition-name in stylesheets and check each selector. Anything more specific than an id is a candidate — a class, an attribute selector, a descendant combinator — because all of them can match more than once. Then search for imperative assignments and confirm each one has a matching clear.
The second half is easier to get wrong and harder to find, because an assignment in one file and its clear in another is a pattern that reads correctly in both places and fails when a code path skips the second. Keeping the assignment and the clear inside the same function, as in the example above, removes the possibility.
Does a name have to be a valid identifier?
It has to be a custom-ident, so the same rules that apply to animation names apply here: no quotes, no leading digits, and none of the reserved keywords. Names are also case-sensitive, which is an occasional source of a pairing that silently does not pair — articleHero and articlehero are different names and neither will complain about the other.
Can two transitions run at once?
No — starting a transition while another is in flight skips or interrupts the first. That is worth knowing here because an interrupted transition still needs its name cleared, which is the case the finally handler exists for. A rapid double activation is the easiest way to reproduce a stale name in testing.
Related
- Cross-Route Element Morphing — the pattern this guide protects
- How @view-transition works under the hood — name pairing and the pseudo-element tree
- View transition snapshot memory cost — why the name count is also a budget
- SPA Page-Swap Animations — the navigation these names attach to