Automatic View Transition Names

Part of View Transitions API Implementation in Modern View Transitions & Scroll APIs.

The problem

A product grid should animate smoothly when the user changes the sort order: every card slides to its new position. With view transitions, each card needs a unique view-transition-name so the browser can pair its old and new positions. The code generates inline styles like style="view-transition-name: card-8f31a" for every item, keeps them unique across pages, removes them after the transition to avoid duplicates, and occasionally crashes the transition when two components render the same product.

Most of that bookkeeping exists only to tell the browser “this element is the same element as before” — something the browser already knows for same-document updates.

Root cause analysis: names are identity, and the DOM has identity

A view transition pairs old and new snapshots by name. Any two captured elements with the same name in the old and new state become one group that morphs; names must be unique at each capture, or the transition is skipped.

For same-document transitions, element identity is available. When document.startViewTransition() updates the DOM, an element that stays in the document — even if it moves — is the same node before and after. The browser can use that node identity as the name.

view-transition-name: match-element does exactly that: each element gets an automatically generated, unique name based on its identity. A reordered card morphs from its old position to its new one, a removed card appears only in the old state, and an added card appears only in the new state — with no strings to manage.

view-transition-name: auto is similar but uses the element’s id attribute as the name when it has one, and falls back to identity otherwise. Matching by id lets two different nodes with the same id — for example, a list item re-rendered by a framework as a new node — still pair up, which match-element would not do.

Automatic names do not cross documents. Cross-document transitions compare two different documents with no shared node identity, so identity-based names cannot match. Those still need explicit names, as covered in implementing cross-document view transitions.

Generated names cannot be targeted by name in CSS. You cannot write ::view-transition-group(card-8f31a) for a name you did not choose. Use view-transition-class to give all automatically named groups a shared class and style ::view-transition-group(.card).

Naming strategies for many elementsAutomatic names remove bookkeeping for same-document updates.Naming strategies for many elementsUnique without codeSurvives node replacementWorks cross-documentExplicitdata-derivednamesNoYesYesmatch-elementYesNoNoauto (id, elseidentity)YesIf ids are stableNo
Automatic names remove bookkeeping for same-document updates.

Step-by-step resolution

Replacing generated namesOne declaration, one class, one fallback.Replacing generated names1Remove script that writes per-item view-transition-name styles.No name bookkeeping2Set view-transition-name: match-element on the item selector.Unique names from node identity3If the framework replaces nodes, give items stable ids and use auto instead.Pairs by id across node replacement4Add view-transition-class: card to the items.Styleable groups5Wrap the sort update in document.startViewTransition().Cards morph to new positions6Fall back to data-derived names where automatic names are unsupported.
One declaration, one class, one fallback.

Production code pattern

.product-card {
  view-transition-name: match-element;     /* unique per node, no strings to manage */
  view-transition-class: card;
}

/* Shared styling for every automatically named card group. */
::view-transition-group(.card) {
  animation-duration: var(--motion-move-duration);
  animation-timing-function: var(--motion-move-easing);
}

/* Cards that exist only before (removed) or only after (added). */
::view-transition-old(.card):only-child { animation: vt-card-out var(--motion-exit-duration) var(--motion-exit-easing) both; }
::view-transition-new(.card):only-child { animation: vt-card-in var(--motion-enter-duration) var(--motion-enter-easing) both; }
@keyframes vt-card-out { to   { opacity: 0; scale: 0.94; } }
@keyframes vt-card-in  { from { opacity: 0; scale: 0.94; } }

/* Fallback for engines without automatic names: names from data ids. */
@supports not (view-transition-name: match-element) {
  .product-card { view-transition-name: var(--vt-name, none); }
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
<li class="product-card" style="--vt-name: product-8f31a">…</li>
function sortProducts(order) {
  const update = () => renderGrid(sort(products, order));
  if (!document.startViewTransition) return update();
  document.startViewTransition(update);
}

Rendering Impact: composite, with one snapshot texture per named card in the old and new states. Automatic naming does not change the cost of a view transition; naming fewer elements does. For very large grids, name only the cards in or near the viewport.

The @supports not fallback relies on the --vt-name custom property written once in markup from the product id — it never changes and needs no cleanup, unlike names added and removed around each transition.

Script and style work around a 60-card sort transitionMain-thread time spent assigning and clearing names, excluding the DOM update.Script and style work around a 60-card sort transitionGenerated names set and cleared per transition14.6 msStatic data-derived names in markup1.2 msmatch-element0.4 ms
Main-thread time spent assigning and clearing names, excluding the DOM update.

Limiting how many elements are named

Automatic naming makes it tempting to name everything. Each named element is captured separately, costing a texture for its old snapshot and one for its new snapshot, and all of them animate as separate groups. A 500-item list sorted in one transition allocates a thousand snapshot textures, most for items far off screen. Restrict naming to what the user can see: apply match-element only to items inside the viewport, for example with a class set by an IntersectionObserver, and let everything else be part of the root cross-fade. The memory arithmetic is in GPU memory and texture management.

Verification checklist

Constraints and trade-offs

  • match-element cannot pair a node with a different node that replaced it; use auto with stable ids or explicit names.
  • Automatic names do not work for cross-document transitions.
  • Naming many elements multiplies snapshot memory.
  • Support for automatic names is newer than view transitions themselves.
  • Generated names cannot be targeted individually in CSS.

Frequently asked questions

What does view-transition-name: match-element do?

It gives each element a unique name based on its identity in the DOM, so the same node before and after a same-document update morphs between its positions without manual naming.

What is the difference between auto and match-element?

auto uses the element’s id attribute as the name when present and falls back to identity. match-element always uses identity.

Do automatic names work across page navigations?

No. Cross-document transitions compare different documents, so there is no shared element identity. Use explicit names.

How do I style automatically named groups?

Add view-transition-class to the elements and target ::view-transition-group(.class) and the related pseudo-elements.