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.