Naming many view transitions: attr() or match-element?
Iris Calderón
You have a CSS grid of cards. You want a view transition that animates every card from its old position to its new one, individually. The rule is non-negotiable: each snapshot needs a unique view-transition-name. The interesting part is no longer that requirement. It is how you assign those names without hand-writing one per card.
Bramus puts the two modern answers next to each other, prompted by Kevin Powell demonstrating one of them at CSS Day and Cyd Stumpel asking about the other in the Q&A. Both produce individual snapshots. They are not the same tool.
The attr() route
Drop a data attribute on each card, then read it from CSS:
.card {
view-transition-name: attr(data-id type(<custom-ident>), none);
view-transition-class: card;
}
attr() resolves data-id into a <custom-ident> and hands the result to view-transition-name. One declaration, every card gets its own name, the values come from the markup. The none fallback covers any card missing the attribute, so the cascade does not blow up on a typo.
view-transition-class: card is the second half of this snippet and the part that earns its place. With every card now carrying a unique name, you would otherwise be writing per-name ::view-transition-group() rules forever. The class lets the snapshots share one set of keyframes.
What match-element does
The newer keyword skips the data attribute entirely:
.card {
view-transition-name: match-element;
view-transition-class: card;
}
match-element derives a view-transition-name from the element's identity in the DOM. Same effect at the snapshot layer: each card moves on its own. You did not have to author the per-item identifier. For a hand-built layout where adding data-id to every card is a chore, that is a real ergonomic upgrade.
Where they diverge
Two snippets that look interchangeable do different things the moment you push them past a single document.
Cross-document navigations are the obvious break. match-element reads from element identity, and a node on page A is not the same node as a node on page B. The keyword has nothing stable to anchor to. attr(data-id …) keeps working because the identifier you care about lives in the markup, and the markup is the thing that crosses documents.
The other break is targeted styling. If your transition needs to single out one snapshot, say a different timing curve on a specific card, you reach for a ::view-transition-*() pseudo-selector that takes a name. Those selectors take a name. attr() gives you a name you can write down and reuse in a rule. match-element does not.
So the choice is not really "which is newer". It is whether you need the name as a value you can refer back to, in a stylesheet rule or across a navigation. If yes, the attribute route still wins. If no, the keyword saves you the markup work.
On your next view transition
Reach for match-element when the transition lives inside a single page and your animation is shared across every member of the group via view-transition-class. That is the case it was built for, and the snippet is two short lines.
Reach for attr() when the same cards need to keep their names across documents, or when you want to address a specific snapshot in a ::view-transition-*() selector by name. The data attribute is doing real work there. It is not ceremony.
Either way, treat view-transition-class as the default companion. The moment you stop hand-writing a name per card, you also stop wanting to hand-write a keyframe rule per name.
Source: Bram.us (bram.us)