{"id":2325,"date":"2026-05-27T18:06:27","date_gmt":"2026-05-27T18:06:27","guid":{"rendered":"https:\/\/tucumandevelopers.com\/index.php\/2026\/05\/27\/cross-document-view-transitions-scaling-across-hundreds-of-elements\/"},"modified":"2026-05-27T18:06:27","modified_gmt":"2026-05-27T18:06:27","slug":"cross-document-view-transitions-scaling-across-hundreds-of-elements","status":"publish","type":"post","link":"https:\/\/tucumandevelopers.com\/index.php\/2026\/05\/27\/cross-document-view-transitions-scaling-across-hundreds-of-elements\/","title":{"rendered":"Cross-Document View Transitions: Scaling Across Hundreds of Elements"},"content":{"rendered":"<div>\n<div>\n<h3 id=\"the-dream-one-line-infinite-names\">The Dream: One Line, Infinite Names<\/h3>\n<p>In a perfect world, you\u2019d solve the scaling problem with pure CSS. No JavaScript. No server-side loops. Just this:<\/p>\n<pre rel=\"CSS\" data-line=\"\"><code markup=\"tt\">.card { \/* Generates card-1, card-2, card-3, etc. automatically *\/ view-transition-name: ident(\"card-\" sibling-index()); }<\/code><\/pre>\n<p>That\u2019s <a href=\"https:\/\/www.bram.us\/2024\/12\/18\/the-future-of-css-construct-custom-idents-and-dashed-idents-with-ident\/\" rel=\"noopener\"><code>ident()<\/code><\/a> \u2014 a CSS function <a href=\"https:\/\/github.com\/w3c\/csswg-drafts\/issues\/9141\" rel=\"noopener\">proposed by Bramus<\/a> (who works on Chrome) to the CSS Working Group. It takes strings, integers, or other identifiers, concatenates them, and spits out a valid CSS name. Pair it with <code>sibling-index()<\/code>, which returns an element\u2019s position among its siblings (1, 2, 3\u2026), and you get auto-generated unique names for every element in a list. One rule. Works for 10 cards or 10,000. The CSS doesn\u2019t care.<\/p>\n<p>And it\u2019s not just view transitions. The same pattern works for <code>scroll-timeline-name<\/code>, <code>container-name<\/code>, <code>view-timeline-name<\/code> \u2014 anywhere you need unique identifiers at scale. You could even pull names from HTML attributes with <code>attr()<\/code> instead of <code>sibling-index()<\/code>, constructing identifiers like <code>ident(\"--item-\" attr(id) \"-tl\")<\/code>. The flexibility is real.<\/p>\n<p>Here\u2019s the thing: half of this equation already exists. <code>sibling-index()<\/code> shipped in Chrome 138 \u2014 you can use it today for things like staggered animations and calculated styles. The missing piece is <code>ident()<\/code>. There\u2019s a <a href=\"https:\/\/chromestatus.com\/feature\/6230159413477376\" rel=\"noopener\">Chrome Intent to Prototype<\/a> from May 2025, which means it\u2019s on the radar. But \u201con the radar\u201d and \u201cin your browser\u201d are very different things. No browser ships <code>ident()<\/code> yet, and there\u2019s no timeline for when it\u2019ll land.<\/p>\n<p>So we can\u2019t use it yet. But it\u2019s worth knowing about because once <code>ident()<\/code> ships, a huge chunk of the complexity you\u2019re about to see just\u2026 evaporates. Until then, here\u2019s how you solve the same problem efficiently today \u2014 with the tools that actually exist in browsers right now.<\/p>\n<h2 id=\"100-products-100-names-1-nightmare\">100 Products, 100 Names, 1 Nightmare<\/h2>\n<p>Here\u2019s what happens when you follow a tutorial that shows one hero image transitioning between two pages and try to apply that pattern to a grid:<\/p>\n<pre rel=\"CSS\" data-line=\"\"><code markup=\"tt\">\/* THE NIGHTMARE - one rule per item, forever *\/ ::view-transition-group(card-1), ::view-transition-group(card-2), ::view-transition-group(card-3), ::view-transition-group(card-4), ::view-transition-group(card-5), ::view-transition-group(card-6), ::view-transition-group(card-7), ::view-transition-group(card-8) \/* ... imagine 92 more of these *\/ { animation-duration: 0.35s; animation-timing-function: ease-out; } ::view-transition-old(card-1), ::view-transition-old(card-2), ::view-transition-old(card-3) \/* kill me *\/ { object-fit: cover; }<\/code><\/pre>\n<p>That\u2019s what you end up with if you follow the tutorials that only show one or two named elements. They assign <code>view-transition-name: hero<\/code> to one image and call it a day. Cool. Now try building a product grid.<\/p>\n<p>Every <code>view-transition-name<\/code> on a page must be unique. That\u2019s a hard rule \u2014 if two elements share a name, the browser doesn\u2019t know which one maps to which on the next page, so it throws the whole transition out. On a listing page with 48 products, you need 48 unique names. On a photo gallery with 200 thumbnails, you need 200. The names aren\u2019t the problem \u2014 you can generate those. The problem is that every pseudo-element selector in your CSS targets a <em>specific name<\/em>, so your animation styles explode into an unmanageable wall of selectors.<\/p>\n<p>This is where you need to understand the difference between two properties that sound like they do the same thing but absolutely do not.<\/p>\n<h3 id=\"name-vs-class-the-distinction-that-changes-everything\">Name vs. Class: The Distinction That Changes Everything<\/h3>\n<p>And yeah, the naming here is confusing. I\u2019ll be honest: the first time I saw <a href=\"https:\/\/css-tricks.com\/almanac\/properties\/v\/view-transition-name\/\"><code>view-transition-name<\/code><\/a> and <a href=\"https:\/\/css-tricks.com\/almanac\/properties\/v\/view-transition-class\/\"><code>view-transition-class<\/code><\/a> next to each other, I thought they were interchangeable. They\u2019re not, and the difference matters.<\/p>\n<p><strong>Name = identity.<\/strong> It answers: \u201cWhich element on Page A is the <em>same element<\/em> on Page B?\u201d When you give a thumbnail <code>view-transition-name: card-7<\/code> on the grid page and give the hero image <code>view-transition-name: card-7<\/code> on the detail page, you\u2019re telling the browser those are the same thing and to animate between them. Names must be unique per page. Two elements can\u2019t both be <code>card-7<\/code> or the whole thing breaks.<\/p>\n<p><strong>Class = styling hook.<\/strong> It answers: \u201cHow should the animation <em>look<\/em>?\u201d When fifty elements all have <code>view-transition-class: card<\/code>, you can write one CSS rule that controls the duration, easing, and <code>object-fit<\/code> for all of them. It\u2019s the same mental model as CSS classes on regular elements \u2014 <code>.btn<\/code> doesn\u2019t identify a specific button, it says \u201cstyle me like a button.\u201d<\/p>\n<p>Think of it like a database. The <code>name<\/code> is the primary key \u2014 unique, identifies one specific row. The <code>class<\/code> is a category column \u2014 groups rows together so you can run a query across all of them at once.<\/p>\n<p>Here\u2019s what that looks like in practice:<\/p>\n<p>There it is. Six cards, six unique names, but exactly <em>three<\/em> CSS rules handling all the animation behavior. Could be sixty cards. Could be six hundred. The CSS doesn\u2019t change.<\/p>\n<p>The key line is that selector: <code>::view-transition-group(*.card)<\/code>. The asterisk is a wildcard for the name, and <code>.card<\/code> matches the <code>view-transition-class<\/code>. It reads as \u201cany view transition group whose element has <code>view-transition-class: card<\/code>, regardless of what its specific name is.\u201d<\/p>\n<p>For cross-document multi-page application (MPA) transitions, the pattern is the same but you generate the names on the server:<\/p>\n<pre rel=\"HTML\" data-line=\"\"><code markup=\"tt\">&lt;!-- Page A --&gt; &lt;div class=\"grid\"&gt; &lt;!-- ... --&gt; &lt;a href=\"\/product\/42\" class=\"card\" style=\"view-transition-name: product-42; view-transition-class: card\" &gt; &lt;img src=\"\/images\/42-thumb.jpg\" alt=\"Widget\" \/&gt; &lt;\/a&gt; &lt;a href=\"\/product\/43\" class=\"card\" style=\"view-transition-name: product-43; view-transition-class: card\" &gt; &lt;img src=\"\/images\/43-thumb.jpg\" alt=\"Gadget\" \/&gt; &lt;\/a&gt; &lt;\/div&gt;<\/code><\/pre>\n<pre rel=\"HTML\" data-line=\"\"><code markup=\"tt\">&lt;!-- Page B --&gt; &lt;div class=\"product-hero\" style=\"view-transition-name: product-42; view-transition-class: card\" &gt; &lt;img src=\"\/images\/42-hero.jpg\" alt=\"Widget\" \/&gt; &lt;\/div&gt;<\/code><\/pre>\n<pre rel=\"CSS\" data-line=\"\"><code markup=\"tt\">\/* ONE stylesheet, shared by all pages, handles every product *\/ @view-transition { navigation: auto; } ::view-transition-group(*.card) { animation-duration: 0.35s; animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94); } ::view-transition-old(*.card), ::view-transition-new(*.card) { object-fit: cover; }<\/code><\/pre>\n<p>That\u2019s the entire animation stylesheet for a site with thousands of products. Three rules. No matter how many items you have in the database, you never add another line of transition CSS.<\/p>\n<p>Before <code>view-transition-class<\/code> existed, people were doing horrifying things \u2014 looping through items in JavaScript to generate <code>&lt;style&gt;<\/code> blocks with hundreds of selectors, or using CSS preprocessors to spit out every possible name permutation at build time. It worked, technically, the same way duct-taping a car bumper works technically.<\/p>\n<p><code>view-transition-class<\/code> is the spec authors acknowledging that the original API just didn\u2019t scale, and fixing it the right way.<\/p>\n<p>One gotcha: <code>view-transition-class<\/code> was added to the spec later to fix these exact scaling issues. The property landed in Chrome 125 and is now in Chrome, Edge, and Safari 18.2+. Older Chromium versions and Firefox won\u2019t recognize it yet. The transitions will still <em>work<\/em>, they\u2019ll just use the default fade animation instead of your custom timing. Not the worst fallback.<\/p>\n<p> <baseline-status featureid=\"view-transition-class\"><\/baseline-status> <\/p>\n<p>You can also assign multiple classes to a single element, just like regular CSS classes. Something like <code>view-transition-class: card featured<\/code> is valid, and you can target it with either <code>::view-transition-group(*.card)<\/code> or <code>::view-transition-group(*.featured)<\/code>. Handy when you want most products to transition the same way but need a few to stand out with a different animation style.<\/p>\n<h2 id=\"don-t-name-everything-upfront\">Don\u2019t Name Everything Upfront<\/h2>\n<p>Everything so far has had <code>view-transition-name<\/code> sitting right there in the HTML or CSS from the moment the page loads. That works. But it has a cost that\u2019s not obvious until you hit real-world scale.<\/p>\n<p>Look at the CSS for both pages. Zero <code>view-transition-name<\/code> declarations. None. Every card in the grid is anonymous until the exact moment the user clicks one.<\/p>\n<p>Here\u2019s why that matters. When you put <code>view-transition-name<\/code> on an element in your stylesheet \u2014 just sitting there in CSS, assigned from page load \u2014 you\u2019re telling the browser, \u201cThis element participates in every transition that happens on this page.\u201d Every single navigation. The browser has to snapshot it, calculate its position, and set up the pseudo-element tree for it. For one hero image, who cares? For a grid of 48 product cards, that\u2019s 48 elements being individually captured, diffed, and animated when the user only clicked <em>one<\/em> of them. The other 47 snapshots are pure waste.<\/p>\n<p>On a fast machine you might not notice. On a mid-range Android phone loading a grid of product images over LTE? You\u2019ll feel it. The transition stutters or the browser just skips it entirely because it can\u2019t set everything up fast enough.<\/p>\n<p>The fix is to treat <code>view-transition-name<\/code> like a just-in-time thing. Assign it at the moment of interaction, not at page load.<\/p>\n<p>The lifecycle goes like this:<\/p>\n<ol>\n<li>User clicks a card on the listing page.<\/li>\n<li>Browser starts navigating \u2014 <code>pageswap<\/code> fires on the old page.<\/li>\n<li>Your <code>pageswap<\/code> handler looks at <code>event.activation.entry.url<\/code> to figure out <em>where<\/em> the user is going, finds the clicked card, slaps <code>view-transition-name: product-42<\/code> on it.<\/li>\n<li>Browser snapshots that one named element (plus the default <code>root<\/code> transition).<\/li>\n<li>Navigation happens, new page loads.<\/li>\n<li><code>pagereveal<\/code> fires on the incoming page.<\/li>\n<li>Your <code>pagereveal<\/code> handler reads the URL, finds the hero element, assigns the matching <code>view-transition-name: product-42<\/code>.<\/li>\n<li>Browser sees matching names on old and new snapshots \u2014 morphs between them.<\/li>\n<li>Transition finishes, your <code>.finished<\/code> promise resolves, you clear the names.<\/li>\n<\/ol>\n<p>That\u2019s it. One element named, one element transitioned, zero waste.<\/p>\n<p>The <code>event.activation<\/code> object is your best friend here. On the outgoing page, <code>event.activation.entry.url<\/code> tells you where the navigation is headed. On the incoming page, you just read <code>window.location<\/code>. Between the two, you have everything you need to figure out which element to name without any global state, no <code>sessionStorage<\/code> tricks, no query parameter gymnastics beyond what your app already uses.<\/p>\n<p>And about that cleanup step, removing the name after <code>.finished<\/code> resolves? It\u2019s not just tidiness. If the user navigates back to the listing page and clicks a <em>different<\/em> card, you don\u2019t want the old card still carrying a name from the previous transition. Stale names cause duplicate-name conflicts (instant transition death) or wrong-element matching (the new page morphs from the wrong card). Clean up after yourself.<\/p>\n<p>This pattern is basically what <a href=\"https:\/\/docs.astro.build\/en\/guides\/view-transitions\/#naming-a-transition\" rel=\"noopener\">Astro\u2019s <code>transition:name<\/code><\/a> directive does under the hood. Same with <a href=\"https:\/\/nuxt.com\/docs\/3.x\/getting-started\/transitions\" rel=\"noopener\">Nuxt\u2019s view transition support<\/a>. They dynamically assign and remove names around the navigation lifecycle. The frameworks just hide the <code>pageswap<\/code>\/<code>pagereveal<\/code> wiring behind a component attribute. You\u2019re doing the same thing, just without the abstraction layer. Fewer moving parts, same result.<\/p>\n<h3 id=\"practical-patterns-for-real-content\">Practical Patterns for Real Content<\/h3>\n<p>The product grid example covers the most common case, but let\u2019s run through a couple of other patterns you\u2019ll hit in the wild.<\/p>\n<h4 id=\"photo-galleries-with-mixed-aspect-ratios\">Photo Galleries with Mixed Aspect Ratios<\/h4>\n<p>Galleries are tricky because every thumbnail might have a different aspect ratio, and the full-size view definitely will. The taffy fix from the Part 1 article is essential here, but you also want the transition to feel intentional rather than chaotic.<\/p>\n<pre rel=\"CSS\" data-line=\"\"><code markup=\"tt\">\/* Gallery items get their own class for targeted animation *\/ ::view-transition-group(*.gallery-item) { animation-duration: 0.5s; animation-timing-function: cubic-bezier(0.2, 0, 0, 1); } ::view-transition-old(*.gallery-item), ::view-transition-new(*.gallery-item) { object-fit: cover; overflow: hidden; } \/* Lightbox-style overlay - fade the background separately *\/ ::view-transition-group(*.lightbox-bg) { animation-duration: 0.3s; }<\/code><\/pre>\n<p>The trick with galleries is assigning the <code>view-transition-name<\/code> to the <code>&lt;img&gt;<\/code> itself rather than the surrounding card or container. You want the browser to morph the image from thumbnail size to lightbox size, not the card\u2019s background, padding, and caption along with it. Name the image. Style the card. Keep them separate.<\/p>\n<p>For the lightbox background (that dark overlay), give it its own <code>view-transition-name<\/code> and <code>view-transition-class<\/code>. It\u2019ll fade in independently while the image morphs. Two transitions running in parallel, each with their own timing. Looks polished, and it\u2019s just two names.<\/p>\n<h4 id=\"tab-or-section-transitions-within-a-page\">Tab or Section Transitions Within a Page<\/h4>\n<p>Not everything is a grid-to-detail pattern. Sometimes you\u2019re transitioning between sections on the same page, e.g., dashboard tabs, multi-step forms, content panels. Same-document view transitions work great here, and the <code>view-transition-class<\/code> approach scales the same way.<\/p>\n<pre rel=\"CSS\" data-line=\"\"><code markup=\"tt\">\/* Shared header that persists across tabs *\/ ::view-transition-group(*.persistent) { animation-duration: 0s; \/* don't animate - it should feel anchored *\/ } \/* Tab content that swaps *\/ ::view-transition-group(*.tab-content) { animation-duration: 0.25s; } ::view-transition-old(*.tab-content) { animation: slide-out-left 0.25s ease-in; } ::view-transition-new(*.tab-content) { animation: slide-in-right 0.25s ease-out; } @keyframes slide-out-left { to { transform: translateX(-100%); opacity: 0; } } @keyframes slide-in-right { from { transform: translateX(100%); opacity: 0; } }<\/code><\/pre>\n<p>The <code>animation-duration: 0s<\/code> on persistent elements is worth calling out. If your site header has a <code>view-transition-name<\/code> (so it stays in place instead of participating in the default root cross-fade), you probably don\u2019t want it animating at all. Zero-duration makes it snap to its new position instantly, which feels like it never moved. That\u2019s the point \u2014 stable landmarks make the transitioning content feel grounded.<\/p>\n<h4 id=\"dynamic-content-and-infinite-scroll\">Dynamic Content and Infinite Scroll<\/h4>\n<p>Here\u2019s a pattern that catches people off guard. You\u2019ve got a product grid with infinite scroll, loading new items as the user scrolls down. Each new batch arrives via <code>fetch()<\/code> and gets appended to the DOM. Do those new items need <code>view-transition-name<\/code>?<\/p>\n<p>No. Not until someone clicks one.<\/p>\n<p>With the just-in-time pattern, it doesn\u2019t matter whether an element existed at page load or was added dynamically five minutes later. The <code>pageswap<\/code> handler queries the DOM at the moment of navigation. If the element is there, it finds it, names it, done. Your infinite scroll items work identically to your initial page load items without any extra setup.<\/p>\n<p>The one thing to watch out for: make sure your <code>data-id<\/code> attributes (or whatever you\u2019re using to match elements) are unique across all loaded batches. If your API returns items with IDs and you\u2019re using those for the <code>view-transition-name<\/code>, you\u2019re already fine. If you\u2019re generating IDs client-side, make sure they don\u2019t collide when new batches load.<\/p>\n<h3 id=\"don-t-make-people-sick\">Don\u2019t Make People Sick<\/h3>\n<pre rel=\"CSS\" data-line=\"\"><code markup=\"tt\">\/* The responsible way to set up view transitions *\/ @view-transition { navigation: auto; } \/* All your animation customizations go INSIDE this media query *\/ @media (prefers-reduced-motion: no-preference) { ::view-transition-group(*.card) { animation-duration: 0.35s; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } ::view-transition-old(*.card), ::view-transition-new(*.card) { object-fit: cover; } \/* Custom keyframes, staggered delays, the fun stuff - all in here *\/ ::view-transition-old(root) { animation: fade-out 0.2s ease-in; } ::view-transition-new(root) { animation: fade-in 0.3s ease-out; } } \/* If the user HAS requested reduced motion: instant cut, no animation *\/ @media (prefers-reduced-motion: reduce) { ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation-duration: 0s !important; } } @keyframes fade-out { to { opacity: 0; } } @keyframes fade-in { from { opacity: 0; } }<\/code><\/pre>\n<p>This isn\u2019t a nice-to-have. I need to be blunt about that.<\/p>\n<p>People with vestibular disorders \u2014 and there are a lot more of them than most developers realize \u2014 can get physically nauseous from unexpected motion on screen. Not \u201cmildly annoyed.\u201d Nauseous. Dizzy. Migraines that last hours. The <a href=\"https:\/\/css-tricks.com\/almanac\/rules\/m\/media\/prefers-reduced-motion\/\"><code>prefers-reduced-motion<\/code><\/a> media query exists because real people checked a box in their OS settings that says \u201cplease stop making me sick.\u201d Ignoring it is the accessibility equivalent of removing a wheelchair ramp because stairs look cleaner.<\/p>\n<p>The <a href=\"https:\/\/css-tricks.com\/almanac\/rules\/v\/view-transition\/\"><code>@view-transition<\/code><\/a> opt-in can stay outside the media query. That\u2019s fine, it just tells the browser, \u201cI want cross-document transitions enabled.\u201d The browser will still do an instant cut between pages, which is visually identical to a normal navigation. It\u2019s the animation customizations that need to be gated: the durations, the easing curves, the custom keyframes. Wrap all of that in <code>prefers-reduced-motion: no-preference<\/code> and you\u2019re covered.<\/p>\n<p>That <code>prefers-reduced-motion: reduce<\/code> block at the bottom is a belt-and-suspenders thing. Even if you miss wrapping some animation rule, forcing <code>animation-duration: 0s<\/code> on all the transition pseudo-elements ensures nothing actually moves. <a href=\"https:\/\/css-tricks.com\/alternatives-to-the-important-keyword\/#when-using-important-does-make-sense\">The <code>!important<\/code> is ugly but justified here.<\/a> you genuinely want this to override everything, no exceptions.<\/p>\n<p>You already saw the conditional opt-in pattern back in Part 1:<\/p>\n<pre rel=\"CSS\" data-line=\"\"><code markup=\"tt\">\/* You can also just disable transitions entirely for reduced-motion users *\/ @media (prefers-reduced-motion: no-preference) { @view-transition { navigation: auto; } }<\/code><\/pre>\n<p>Either approach works. Wrapping the whole <code>@view-transition<\/code> rule means the browser won\u2019t even attempt the transition \u2013 it\u2019s a normal navigation, full stop. Keeping <code>@view-transition<\/code> active but killing the animation durations means the transition technically fires but completes instantly, which can matter if you have <code>pagereveal<\/code> logic that depends on <code>event.viewTransition<\/code> existing. Pick whichever fits your setup. Just don\u2019t ship animated transitions without checking.<\/p>\n<p>A thing worth considering here: <a href=\"https:\/\/css-tricks.com\/nuking-motion-with-prefers-reduced-motion\/\">\u201creduced motion\u201d doesn\u2019t necessarily mean \u201cno motion.\u201d<\/a> Some users with vestibular sensitivities are fine with fades but not with sliding or zooming. You could offer a gentler alternative instead of killing all animation entirely.<\/p>\n<pre rel=\"CSS\" data-line=\"\"><code markup=\"tt\">@media (prefers-reduced-motion: reduce) { \/* Instead of zero duration, use a quick crossfade only *\/ ::view-transition-group(*) { animation-duration: 0.15s !important; animation-timing-function: linear !important; } ::view-transition-old(*) { animation: fade-out 0.15s linear !important; } ::view-transition-new(*) { animation: fade-in 0.15s linear !important; } }<\/code><\/pre>\n<p>This is a judgment call. A fast, subtle cross-fade is less likely to trigger symptoms than a <code>400ms<\/code> morphing animation with easing curves. But the safest option is always zero motion, and if you\u2019re not sure, go with <code>animation-duration: 0s<\/code>. You can always add a gentler alternative later once you\u2019ve tested it with actual users who rely on the setting.<\/p>\n<h3 id=\"handle-old-browsers-by-doing-basically-nothing-\">Handle Old Browsers (By Doing Basically Nothing)<\/h3>\n<pre rel=\"CSS\" data-line=\"\"><code markup=\"tt\">\/* Feature detection, if you need it *\/ @supports (view-transition-name: none) { .card { \/* maybe you want contain: paint for better snapshotting *\/ contain: paint; } }<\/code><\/pre>\n<pre rel=\"JavaScript\" data-line=\"\"><code markup=\"tt\">\/\/ JS-side feature detection if (document.startViewTransition) { \/\/ same-document transition API exists } \/\/ For cross-document transitions, there's no direct JS check - \/\/ the browser either supports @view-transition in CSS or ignores it. \/\/ That's... actually fine.<\/code><\/pre>\n<p>Here\u2019s the thing though: you probably don\u2019t even need that <code>@supports<\/code> check.<\/p>\n<p>View transitions are progressive enhancement in the purest sense of the term. If a browser doesn\u2019t understand <code>@view-transition { navigation: auto; }<\/code>, it ignores the rule. That\u2019s how CSS works. The user clicks a link, the browser navigates normally, the new page loads. No animation, no morphing, no cross-fade. Just a regular page load. Which is exactly what every website on the internet did for the first 25 years of the web. It\u2019s fine.<\/p>\n<p>Nothing breaks. No JavaScript errors. No layout shifts. No fallback code to write. The <code>view-transition-name<\/code> properties get ignored. The <code>::view-transition-*<\/code> pseudo-element selectors match nothing. Your <code>pageswap<\/code> and <code>pagereveal<\/code> event listeners either don\u2019t fire or <code>event.viewTransition<\/code> is <code>null<\/code> and your guard clause returns early. The whole feature is designed to be invisible when it\u2019s absent.<\/p>\n<p>That\u2019s the beauty of this API, honestly. It\u2019s one of the rare web platform features where you don\u2019t have to write a single line of fallback code. Firefox doesn\u2019t support it yet? Fine \u2014 Firefox users get normal navigation. Safari\u2019s working on it but hasn\u2019t shipped? Cool, Safari users click links and pages load. Nobody gets an error. Nobody gets a broken layout. Nobody loses anything. They just don\u2019t get the fancy animation, and most of them will never notice it was supposed to be there.<\/p>\n<p>Worth noting where things actually stand today: Chrome and Edge have full support for cross-document view transitions, including view-transition-class. Safari also ships full cross-document support as of Safari 18.2. The momentum is clearly toward universal support, even though Firefox still holds it behind a flag for now.<\/p>\n<p>The only time <code>@supports<\/code> matters is if you\u2019re adding styles that <em>only<\/em> make sense in the context of view transitions \u2014 like <code>contain: paint<\/code> on elements to improve snapshot quality, or hiding some loading state that the transition would normally cover. Gate those behind <code>@supports (view-transition-name: none)<\/code> so non-supporting browsers don\u2019t get the side effects without the payoff.<\/p>\n<p>Failure is invisible. That\u2019s the whole point.<\/p>\n<h3 id=\"ship-it\">Ship It<\/h3>\n<p>Look, I\u2019ve been building websites for a long time, and there\u2019s always been this unspoken trade-off: you want smooth, app-like transitions, you adopt a framework and a client-side router and a build step and a hydration strategy and suddenly you\u2019re maintaining a small aircraft carrier just so a card can animate into a hero image.<\/p>\n<p>That trade-off is dissolving.<\/p>\n<p>Cross-document view transitions let an <code>&lt;a href&gt;<\/code> feel like a native app navigation. Two HTML files. Some CSS. Maybe a little JavaScript for the fancy stuff. The browser does the rest. That\u2019s not a small thing \u2013 it changes which projects <em>need<\/em> a framework and which ones just <em>assumed<\/em> they did.<\/p>\n<p>The spec is young. It\u2019s Chromium-only right now. The rough edges are real \u2013 you\u2019ve seen them across both parts of this series. But the API is designed so well that when it\u2019s not supported, nothing breaks. Your site just works the way sites have always worked. And when it <em>is<\/em> supported, it feels like magic that came free.<\/p>\n<p>Here\u2019s a quick cheat sheet to take with you:<\/p>\n<ul>\n<li><strong>Opt in with CSS<\/strong>, not the deprecated meta tag: <code>@view-transition { navigation: auto; }<\/code>.<\/li>\n<li><strong>Both pages<\/strong> must opt in or no transition happens.<\/li>\n<li><strong>4-second timeout<\/strong> starts at navigation, not at render \u2013 use <code>pagereveal<\/code> to catch <code>TimeoutError<\/code>.<\/li>\n<li><strong>Images stretch<\/strong> during transitions because pseudo-elements default to <code>object-fit: fill<\/code> \u2013 fix it with <code>object-fit: cover<\/code> on <code>::view-transition-old<\/code> and <code>::view-transition-new<\/code>.<\/li>\n<li><strong><code>view-transition-name<\/code><\/strong> = identity (unique per page), <strong><code>view-transition-class<\/code><\/strong> = styling hook (shared across elements).<\/li>\n<li><strong>Don\u2019t name elements upfront<\/strong> \u2013 use <code>pageswap<\/code> and <code>pagereveal<\/code> to assign names just-in-time. But keep your <code>pageswap<\/code> logic fast \u2014 the browser gives you a narrow window (10-50ms) before snapshots.<\/li>\n<li><strong>Clean up names<\/strong> after <code>viewTransition.finished<\/code> resolves to avoid stale conflicts.<\/li>\n<li><strong>Gate animations<\/strong> behind <code>prefers-reduced-motion: no-preference<\/code> \u2014 this is not optional.<\/li>\n<li><strong>Progressive enhancement<\/strong> is built in \u2014 unsupported browsers just get normal page loads<\/li>\n<\/ul>\n<p>The best animations are the ones you don\u2019t have to maintain a framework to get.<\/p>\n<div>\n<h4 id=\"crossdocument-view-transitions-series\">Cross-Document View Transitions Series<\/h4>\n<ol>\n<li><strong><a href=\"https:\/\/css-tricks.com\/cross-document-view-transitions-part-1\/\">The Gotchas Nobody Mentions<\/a><\/strong><\/li>\n<li><strong>Scaling View Transitions Across Hundreds of Elements<\/strong> <em>(You are here!)<\/em><\/li>\n<\/ol><\/div>\n<\/p><\/div>\n<\/div>\n<\/div>\n<\/div>\n<p>Fuente: <a href=\"https:\/\/css-tricks.com\/cross-document-view-transitions-part-2\/\">Art\u00edculo original<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The Dream: One Line, Infinite Names In a perfect world, you\u2019d solve the scaling problem with pure CSS. No JavaScript. No server-side loops. Just this: .card { \/* Generates card-1, card-2, card-3, etc. automatically *\/ view-transition-name: ident(&#8220;card-&#8221; sibling-index()); } That\u2019s ident() \u2014 a CSS function proposed by Bramus (who works on Chrome) to the CSS [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":2324,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[42],"tags":[],"class_list":["post-2325","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-csstricks"],"jetpack_publicize_connections":[],"_links":{"self":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/2325","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/comments?post=2325"}],"version-history":[{"count":0,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/2325\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media\/2324"}],"wp:attachment":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media?parent=2325"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/categories?post=2325"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/tags?post=2325"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}