> All in One 586

Ads

Wednesday, May 27, 2026

Showers today!



With a high of F and a low of 51F. Currently, it's 55F and Showers in the Vicinity outside.

Current wind speeds: 0 from the Northwest

Pollen: 4

Sunrise: May 27, 2026 at 05:29PM

Sunset: May 28, 2026 at 08:08AM

UV index: 0

Humidity: 96%

via https://ift.tt/qY3MokE

May 28, 2026 at 10:02AM

Revealing Text With CSS letter-spacing

Some text effects are relatively hard to pull in CSS, the main reason being we are unable to target individual characters (something many of us want in the form of ::nth-letter(), although we have basis for it with ::first-letter that gives us access to a box element’s first glyph.

But maybe there are a few things we can use today with what we already have.

For example, the CSS letter-spacing property adjusts the space between all characters in a block of text. Positive values add space to the right side of each glyph (in a left-to-right writing mode), and negative values shrink the width of the glyph box, causing letters to overlap and even move the other way.

The letter-spacing accepts length units, and percentage (relative to font size). It is animateable, and as we saw before, the negative values can shrink it down or reverse it. Which is something we can make use of.

Overlapping and separating letters

It’s quite easy to completely overlap the characters as a starting point and setting it’s color to transparent to visually hide it.

label {
  letter-spacing: -1ch;
  color: transparent;
  /* etc. */
}

From there, we can reveal the text by animating that letter-spacing value to a positive value and updating the color to a visible value, like when a checkbox is :checked:

li:nth-of-type(2) label {
  text-align: center;
}
li:nth-of-type(3) label {
  text-align: right;
}
input:checked + label {
  letter-spacing: 0ch;
  color: black;
  transition: letter-spacing 0.6s, color 0.4s;
}

Note: The CSS ch unit is a relative length representing the width of the zero (0) glyph.

The labels go from negative letter-spacing to normal spacing and the color updates to black. Both these changes happen over a transition.

The second and third labels are given center and right text alignments and thus when negative letter spacing is applied they bundle up at the given alignment position, center and right, respectively. When letter``-``spacing goes from negative to zero (or any positive value) the letters separate from that same alignment position.

Thus, we get a text reveal effect! Let’s look at some more.

Showing and hiding text

Check this out. We can toggle a checkbox label as a fun interactive UI touch:

<!-- Simplified for brevity; additional accessibility considerations -->
<input type="checkbox" id="cb">
<label for="cb">
  <span>Join the global club</span>
  <span>You've begun your journey!</span>
</label>
label {
  overflow: clip;
  /* etc. */
}

span {
  /* The first label */
  &:nth-of-type(1) {
    /* Default spacing: letters are fully visible */
    letter-spacing: 0ch;
    /* When the checkbox is checked, target this text */
    :checked + * & {
      /* collapse letters on top of each other, hiding them */
      letter-spacing: -2ch;
      text-indent: -1.5ch;
      /* Use a "bouncy" cubic-bezier for spacing */
      transition: 0.4s letter-spacing cubic-bezier(.8, -.5, .2, 1.4), 
                  0.1s text-indent;
    }
  }
  
  /* The second label */
  &:nth-of-type(2) { 
    /* Initially collapsed (letters overlap) */
    letter-spacing: -1ch;
    color: transparent;
    /* When the checkbox is checked, target this text */
    :checked + * & {
      /* Returns to normal spacing */
      letter-spacing: 0ch;
      color: black;
      /* Slightly delay the appearance so it starts after the first text begins to hide */
      transition:
        0.4s letter-spacing cubic-bezier(.8, -.5, .2, 1.4) 0.3s, 
        0.8s color 0.4s;
    }
  }
}

When the box is checked, a negative letter-spacing value (-2ch) and text-indent value (-1.5ch) is used on the first <span> to slide it out of the container box. We use overflow: clip to completely hide the text.

Concurrently, the text in the second <span> text goes from a letter-spacing value of -1ch to 0ch, which reveals it. To hide this overlapped text at -1ch, a transparent color was given that’s turned to black when the checkbox is checked.

Using with other glyph box styling

Here’s another fun one. We can start with an acronym that reveal the full text on hover. Again, we have existing features to help us pull this off, including ::first-letter and ::first-line.

We’ll start with this markup:

<!-- Simplified for brevity -->
<p id="acronym">
  <span class="words">United</span>
  <span class="words">Nations</span>
  <span class="words">International</span>
  <span class="words">Children's</span>
  <span class="words">Emergency</span>
  <span class="words">Fund</span>
</p>
.words {
  letter-spacing: -1ch;
  color: transparent;
  /* etc. */

  &::first-letter {
    color: black;
  }

  figure:hover + #acronym & {
    letter-spacing: 0ch;
    color: black;
    transition: letter-spacing 0.4s cubic-bezier(.8, -.5, .2, 1.4) /* etc. */;
  }
}

Each word in the UNICEF acronym initially has letter-spacing: -1ch to shrink the text, and color: transparent to keep the shrunk text hidden, except the ::first-letter that has color: black so it remains visible even though the rest of the text is stacked beneath it.

Now, we can target the image on :hover and select the entire text so that the letter-spacing value for each word decreases to 0ch and color: black is applied, showing what’s remaining of the words:

What else can we do?

I don’t know! But that’s where you come in. Obviously, a hypothetical ::nth-letter selector would be amazing for all kinds of text effects. But it’s neat that we can create some semblance of it today with existing features, like letter-spacing, ::first-letter, and ::first-line.

What can you cook up knowing we have these constraints?


Revealing Text With CSS letter-spacing originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.



from CSS-Tricks https://ift.tt/ZTqm78w
via IFTTT

Tuesday, May 26, 2026

Mostly Cloudy today!



With a high of F and a low of 50F. Currently, it's 66F and Partly Cloudy outside.

Current wind speeds: 14 from the Southeast

Pollen: 3

Sunrise: May 26, 2026 at 05:30PM

Sunset: May 27, 2026 at 08:07AM

UV index: 0

Humidity: 50%

via https://ift.tt/2y5uiJh

May 27, 2026 at 10:02AM

Technical Writing in the AI Age

This isn’t coming out of nowhere:

My apathy levels for the industry are absolutely sky high and I don’t even have a dickhead boss making me hit token targets

Andy Bell (@bell.bz) 2026-05-19T18:23:42.034Z

More and more people I deeply respect and have learned a lot from over the years feel like they are speaking into a void, to the point where they are losing motivation to continue making content, which got me to write my first post on my site in over a year…https://ift.tt/p15BxtS…

Kevin Powell (@kevinpowell.co) 2026-05-21T13:22:13.240Z

It's not fun to make tech content as much anymore, too. I'm doing some because I actually like it, but it feels like I'm shouting into a slop-driven void nearly everywhere. It's really tiring.I get so much joy reading individual blogs and newsletters still (like yours!), at least.

Cassidy (@cassidoo.co) 2026-05-19T22:32:55.483Z

I’m just 7 videos away from finishing a 45-video course about building for the web, and the fact that people are rarely choosing to learn from human-crafted content anymore therefore rendering hundreds of hours of my human effort completely pointless is burning me out catastrophically. I am a husk.

⭑ salma (@whitep4nth3r.com) 2026-05-20T07:05:14.119Z

These are all folks I deeply admire and I’d be lying if I said I disagreed with any of that. The fact is, demand for front-end technical writing has significantly dropped, and has been for some time. Just look at our stats for yourself:

I’m not embarrassed to share this because it’s happening across the board. I mean, it’s the same thing over at Stack Overflow as we’ve already discussed in depth. I work with other content creators and publications and it’s also the same story.

Naturally, I tend to take personal responsibility for this sort of thing, but it’s hard not to see that the root cause might be out of my hands:

Source: 2025 AI Index, Stanford HAI

And yes, I’m pointing squarely at AI. It’s a little unfair to say there aren’t other factors out there impacting our work and this site specifically, but let’s call a spade a spade and identify the biggest headwind.

This isn’t totally about AI. It’s about technical writing in the age of AI. I have some thoughts on this and I hope it’s helpful to you humans reading.

We still need technical writing

The bots certainly aren’t learning new things on their own, right? They’re only as motivated as the prompt they’re given, and it’s human desire and effort that moves things forward.

I want to acknowledge right away that I am not suggesting that AI is the new primary audience for technical writing. It’s real people like you with the motivation to learn that keeps this practice alive, even if your learnings become the prompts that allow AI to generate code for you.

Finding your place

Who (or what) are you writing for? It may be that your place in creating technical content needs a shift. For example, I’d wager that creating technical documentation is less valuable than it once was. Not only do we have a wealth of that in the specs and MDN, but those sorts of developer questions are now being answered directly in a chat that’s embedded right in the IDE.

“But CSS-Tricks is full of all that!” you might say. And it’s true. We have a very extensive Almanac of CSS goodness that’s grown, evolved, and been tended since, I believe, 2009. We even expanded it just a couple years ago. That’s because I think CSS-Tricks has a clear place in the AI age…

…and that’s making technical documentation more accessible to humans. Have you read the specs? They’re dense, and by design for accuracy and clarity for browsers implementing CSS features. They are very technical explanations for very technical topics. I like to think that CSS-Tricks is that place you come to for very human explanations for very technical topics — more like sitting across the table from another developer over coffee (or kombucha, whatever). We serve the same sort of purpose but make a unique experience out of it. It’s all about lowering the barriers to access around here!

But we’re also all about being a platform for great ideas. This site used to be a personal learning space for Chris Coyier when he started it way back in 2007. But that completely changed when Chris opened things up to guest authors. That’s a how I started here in 2015. And we’ve brought in 748 authors (as I write this) since then. Just think of all the great ideas, tips — and yes — tricks that have come out that we may have missed without a CSS-Tricks soapbox to share them. I’ve quite literally learned everything I know about front-end from these folks.

Is it still worth it?

Only you can answer that. Is it about money? Is it about popularity? Is it about craft? Is it about personal learning? Some combination of things? I’m not here to say any of those is better than the others, but there’s definitely a reason you would decide to continue writing technical pieces (or any sort of writing, really) in this age.

I think I’ve made a case for why I do it. But you can still think it’s not worth doing it at all, even if you believe you have purpose in it. Maybe it’s unsustainable to run a blog or newsletter financially at this scale. Maybe other things are competing for your time. Maybe a really great new job is more attractive.

Or maybe you’re simply burned out. You saw the comments at the top of this. The time, effort, and expectations of a job like this are sky high and the payoff — whatever you measure that in — might not be enough. If that’s the case, then the outcome is apathy and burnout. I certainly feel that and admit some days don’t feel as good as others.

But I still think I/CSS-Tricks have a place to fill. And there’s still a good deal of personal fulfillment in that every time an article comment, email, or social mention tells us our work is helping in some way. Really. And I’d suggest letting your favorite technical writers know you enjoy and rely on their work wherever you happen to be reading them.

It’s a pendulum swing of sorts

No, no, no. I don’t see a future where AI suddenly disintegrates and we’re writing code entirely by hand again.

What I mean is that technical writing — and creative web design as a whole — has kinda been here before. Sharing what we learn as we create new and interesting things was once totally against the grain, right? I love how Jay Hoffman documented that in our History archives:

Articles that appeared on Word were one-of a kind, where the images, backgrounds and colors chosen helped facilitate the tone of a piece. These art-directed posts pulled from Levy’s signature style, a blend of 8-bit graphics and off-kilter layouts, with the chaotic bricolage of punk rock zines. Pages came alive, representing through design the personality of the post’s author.

Web design was so punk rock in the 90s and early 2000s. Then it became a discipline. Now it’s becoming automated. And, as such, human web design is punk rock once again. Or at least it can be.

New advice for technical writing

We published a comprehensive piece with technical writing advice in 2019. I don’t think I’d subtract or change any of it. Everything in there is still as relevant and true today as it was then. The goal is writing on a timely topic for a specific audience in the clearest way possible that’s inclusive of different learning styles.

But I do think I’d add to that piece as the landscape continues to shift. My advice?

Um, maybe don’t use AI

For one, we know it’s not always accurate. Two, it dilutes your personal voice. Both are very detrimental to the discipline of writing. And what’s the point of giving people an AI explanation of something they can already get way more easily in their IDE? That’s exactly what leads to AI slop.

But I won’t dismiss AI usage wholesale. I’ll actually caveat the whole “don’t use AI thing” to pin it specifically to writing. Because a tool like Grammarly has always been helpful, even before it was marketed as a complete AI writing suite. You don’t have to use the beefier features that practically do the writing for you to get something out of it.

I tend to reach for AI to help with menial tasks: Quick spell checks, converting Markdown to HTML, keeping the publishing calendar on schedule, things like that. AI is pretty good with low-lift work that doesn’t have much or anything to do with writing.

(I’d also argue that a lot of what we call “AI” today is what we called “automation” before the hype. I’m more interested in the tools that help enhance our craft, not replace it.)

More real-life experiences, less documentation

If you’re going to write about something, I’d lean into what AI can’t solve on its own, or at least can’t solve easily. It’s already really great at giving you the basic definition of any CSS property along with a quick code example, often pulled straight from existing technical documentation. That’s well-trodden territory unless you’re packaging it a different way.

I learn best when I’m faced with a challenge, like something a client wants that I’ve never attempted before. I’d bet the dime in my pocket that’s how you learn best, too. We make mistakes and learn from them. And for technical writers, it’s the path from noob to understanding that makes all the difference. That’s the hook. That gives your readers a mental model to use in their own work — again, even if that mental model is going straight into writing AI prompts.

Then again, I’m the product of the View Source generation. The same sort of way that many musicians would learn by slowing down a record player to learn chords, notes, or beats. Maybe I’m the old man shouting at clouds.

You don’t have to be the authority

I know there’s at least a little expectation that everything we publish on sites like CSS-Tricks are the “right” way to do this or that and that you ought to be able to copy-paste code snippets straight-up. I get that, but also don’t believe that’s a promise this site has ever made. I really do believe that CSS is poetic in the sense that there’s more than one way to do something, and the best way to do that thing is what fits your mental model the most snugly.

And I believe there is real value in trying something purely for the sake of trying it.

This site has always been about exploring, experimenting, and sharing along the way. And sometimes that means sharing just the germ of an idea. There’s no dogma here.

The goal: be experienced, not cynical. You don’t have to be the be-all-end-all authority on a subject for permission to share. Just experience, and that’s not always the same as being the expert.

All of that really to say:

Learn fast, share often

What you learn is going to be more meaningful to a real person than some generated replacement of s Stack Overflow answer.

  • Did you try something and it didn’t work out?
  • Did you try something and it did work out?
  • Did you make a bunch of mistakes on the way to getting it right?
  • Do you still have open questions about the thing you learned?

That’s all worth sharing. That’s where the real-life experiences come from and the learning paths that you simply don’t get from generated responses claiming to have the best answer.

Said another way: Solve real problems. Get into the edge cases. Leverage community knowledge.

Cite generously

At the end of the day, it’s all about humans helping humans. We don’t wake up one day with all the answers. We learn from the things other people learn and share, and it’s worth pointing your readers in their direction. It’s easy to want to come off as the original source of everything you publish, but my experience is that’s never the true case.

We build off each other. It’s blogging at its core. Let’s build each other up the good ol’ fashioned way with hyperlinks.

Forget about SEO… or even AIO

We toe-dipped into SEO several years ago, but it’s never really been our jam. We’ve ridden and enjoyed the Organic Search Train forever. That’s liberating because we never really worry about keyword stuffing, click-baity headlines, or anything really optimization for that matter. We focus instead on writing for humans, good structure, and maintaining a certain voice and tone. All things Google used to care about.

I say “used to care” there because that core value contains less value than ever, even if they do still claim it does.

There’s no doubt about it; search traffic is gutted in favor of generated responses to queries connected to the services you use. That’s a clear hit to traffic and that’s a clear hit to the traditional ways we generate revenue that are measured by traffic analytics.

And forget about whatever it is that’s called “Artificial Intelligence Optimization” (AIO). It’s still an evolving space of fluid and inconsistent “best” practices, like some agents keen on proposals for text-based files to help LLMs use your site that other agents, like Google, just aren’t into. Maybe it’ll become a discipline with clearer strategies, guidelines, and best practices in the future, but we just don’t know, and I’m feigning ignorance at least until the smoke clears.

I’m honestly unsure whether I even care about CSS-Tricks popping up as a reference in a generated response. The landscape is evolving, and my thoughts on it have to as well.

You’ve got this.

I’m rooting for you. I’m rooting for all of us.


Technical Writing in the AI Age originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.



from CSS-Tricks https://ift.tt/mYDTxXC
via IFTTT

Monday, May 25, 2026

Showers/Wind Early today!



With a high of F and a low of 53F. Currently, it's 67F and Clear outside.

Current wind speeds: 16 from the South

Pollen: 3

Sunrise: May 25, 2026 at 05:30PM

Sunset: May 26, 2026 at 08:06AM

UV index: 0

Humidity: 43%

via https://ift.tt/2tcdzZ7

May 26, 2026 at 10:02AM

Cross-Document View Transitions: Scaling Across Hundreds of Elements

In Part 1, we covered the gotchas that bite you first: the deprecated meta tag that silently does nothing, the 4-second timeout that kills transitions without telling you, the image distortion that turns every aspect ratio change into silly putty, and the pagereveal/pageswap events that give you hooks into the transition lifecycle.

All of that gets you from “nothing works” to “one element transitioning nicely between two pages.” Which feels great. For about five minutes. Then you try to build a product listing page with 48 cards that each need to morph into a detail view, and you realize the tutorials left out the hard part.

This is where it gets real. Let’s scale this thing.

Cross-Document View Transitions Series

  1. The Gotchas Nobody Mentions
  2. Scaling View Transitions Across Hundreds of Elements (You are here!)

The Dream: One Line, Infinite Names

In a perfect world, you’d 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("card-" sibling-index());
}

That’s ident() — a CSS function proposed by Bramus (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 sibling-index(), which returns an element’s position among its siblings (1, 2, 3…), 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’t care.

And it’s not just view transitions. The same pattern works for scroll-timeline-name, container-name, view-timeline-name — anywhere you need unique identifiers at scale. You could even pull names from HTML attributes with attr() instead of sibling-index(), constructing identifiers like ident("--item-" attr(id) "-tl"). The flexibility is real.

Here’s the thing: half of this equation already exists. sibling-index() shipped in Chrome 138 — you can use it today for things like staggered animations and calculated styles. The missing piece is ident(). There’s a Chrome Intent to Prototype from May 2025, which means it’s on the radar. But “on the radar” and “in your browser” are very different things. No browser ships ident() yet, and there’s no timeline for when it’ll land.

So we can’t use it yet. But it’s worth knowing about because once ident() ships, a huge chunk of the complexity you’re about to see just… evaporates. Until then, here’s how you solve the same problem efficiently today — with the tools that actually exist in browsers right now.

100 Products, 100 Names, 1 Nightmare

Here’s 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:

/*  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;
}

That’s what you end up with if you follow the tutorials that only show one or two named elements. They assign view-transition-name: hero to one image and call it a day. Cool. Now try building a product grid.

Every view-transition-name on a page must be unique. That’s a hard rule — if two elements share a name, the browser doesn’t 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’t the problem — you can generate those. The problem is that every pseudo-element selector in your CSS targets a specific name, so your animation styles explode into an unmanageable wall of selectors.

This is where you need to understand the difference between two properties that sound like they do the same thing but absolutely do not.

Name vs. Class: The Distinction That Changes Everything

And yeah, the naming here is confusing. I’ll be honest: the first time I saw view-transition-name and view-transition-class next to each other, I thought they were interchangeable. They’re not, and the difference matters.

Name = identity. It answers: “Which element on Page A is the same element on Page B?” When you give a thumbnail view-transition-name: card-7 on the grid page and give the hero image view-transition-name: card-7 on the detail page, you’re telling the browser those are the same thing and to animate between them. Names must be unique per page. Two elements can’t both be card-7 or the whole thing breaks.

Class = styling hook. It answers: “How should the animation look?” When fifty elements all have view-transition-class: card, you can write one CSS rule that controls the duration, easing, and object-fit for all of them. It’s the same mental model as CSS classes on regular elements — .btn doesn’t identify a specific button, it says “style me like a button.”

Think of it like a database. The name is the primary key — unique, identifies one specific row. The class is a category column — groups rows together so you can run a query across all of them at once.

Here’s what that looks like in practice:

There it is. Six cards, six unique names, but exactly three CSS rules handling all the animation behavior. Could be sixty cards. Could be six hundred. The CSS doesn’t change.

The key line is that selector: ::view-transition-group(*.card). The asterisk is a wildcard for the name, and .card matches the view-transition-class. It reads as “any view transition group whose element has view-transition-class: card, regardless of what its specific name is.”

For cross-document multi-page application (MPA) transitions, the pattern is the same but you generate the names on the server:

<!-- Page A -->
<div class="grid">

  <!-- ... -->

  <a
    href="/product/42"
    class="card"
    style="view-transition-name: product-42; view-transition-class: card"
  >
    <img src="/images/42-thumb.jpg" alt="Widget" />
  </a>
  <a
    href="/product/43"
    class="card"
    style="view-transition-name: product-43; view-transition-class: card"
  >
    <img src="/images/43-thumb.jpg" alt="Gadget" />
  </a>
</div>
<!-- Page B -->
<div
  class="product-hero"
  style="view-transition-name: product-42; view-transition-class: card"
>
  <img src="/images/42-hero.jpg" alt="Widget" />
</div>
/* 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;
}

That’s 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.

Before view-transition-class existed, people were doing horrifying things — looping through items in JavaScript to generate <style> 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.

view-transition-class is the spec authors acknowledging that the original API just didn’t scale, and fixing it the right way.

One gotcha: view-transition-class 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’t recognize it yet. The transitions will still work, they’ll just use the default fade animation instead of your custom timing. Not the worst fallback.

You can also assign multiple classes to a single element, just like regular CSS classes. Something like view-transition-class: card featured is valid, and you can target it with either ::view-transition-group(*.card) or ::view-transition-group(*.featured). Handy when you want most products to transition the same way but need a few to stand out with a different animation style.

Don’t Name Everything Upfront

Everything so far has had view-transition-name sitting right there in the HTML or CSS from the moment the page loads. That works. But it has a cost that’s not obvious until you hit real-world scale.

Look at the CSS for both pages. Zero view-transition-name declarations. None. Every card in the grid is anonymous until the exact moment the user clicks one.

Here’s why that matters. When you put view-transition-name on an element in your stylesheet — just sitting there in CSS, assigned from page load — you’re telling the browser, “This element participates in every transition that happens on this page.” 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’s 48 elements being individually captured, diffed, and animated when the user only clicked one of them. The other 47 snapshots are pure waste.

On a fast machine you might not notice. On a mid-range Android phone loading a grid of product images over LTE? You’ll feel it. The transition stutters or the browser just skips it entirely because it can’t set everything up fast enough.

The fix is to treat view-transition-name like a just-in-time thing. Assign it at the moment of interaction, not at page load.

The lifecycle goes like this:

  1. User clicks a card on the listing page.
  2. Browser starts navigating — pageswap fires on the old page.
  3. Your pageswap handler looks at event.activation.entry.url to figure out where the user is going, finds the clicked card, slaps view-transition-name: product-42 on it.
  4. Browser snapshots that one named element (plus the default root transition).
  5. Navigation happens, new page loads.
  6. pagereveal fires on the incoming page.
  7. Your pagereveal handler reads the URL, finds the hero element, assigns the matching view-transition-name: product-42.
  8. Browser sees matching names on old and new snapshots — morphs between them.
  9. Transition finishes, your .finished promise resolves, you clear the names.

That’s it. One element named, one element transitioned, zero waste.

The event.activation object is your best friend here. On the outgoing page, event.activation.entry.url tells you where the navigation is headed. On the incoming page, you just read window.location. Between the two, you have everything you need to figure out which element to name without any global state, no sessionStorage tricks, no query parameter gymnastics beyond what your app already uses.

And about that cleanup step, removing the name after .finished resolves? It’s not just tidiness. If the user navigates back to the listing page and clicks a different card, you don’t 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.

This pattern is basically what Astro’s transition:name directive does under the hood. Same with Nuxt’s view transition support. They dynamically assign and remove names around the navigation lifecycle. The frameworks just hide the pageswap/pagereveal wiring behind a component attribute. You’re doing the same thing, just without the abstraction layer. Fewer moving parts, same result.

Practical Patterns for Real Content

The product grid example covers the most common case, but let’s run through a couple of other patterns you’ll hit in the wild.

Photo Galleries with Mixed Aspect Ratios

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.

/* 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;
}

The trick with galleries is assigning the view-transition-name to the <img> 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’s background, padding, and caption along with it. Name the image. Style the card. Keep them separate.

For the lightbox background (that dark overlay), give it its own view-transition-name and view-transition-class. It’ll fade in independently while the image morphs. Two transitions running in parallel, each with their own timing. Looks polished, and it’s just two names.

Tab or Section Transitions Within a Page

Not everything is a grid-to-detail pattern. Sometimes you’re 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 view-transition-class approach scales the same way.

/* 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;
  }
}

The animation-duration: 0s on persistent elements is worth calling out. If your site header has a view-transition-name (so it stays in place instead of participating in the default root cross-fade), you probably don’t want it animating at all. Zero-duration makes it snap to its new position instantly, which feels like it never moved. That’s the point — stable landmarks make the transitioning content feel grounded.

Dynamic Content and Infinite Scroll

Here’s a pattern that catches people off guard. You’ve got a product grid with infinite scroll, loading new items as the user scrolls down. Each new batch arrives via fetch() and gets appended to the DOM. Do those new items need view-transition-name?

No. Not until someone clicks one.

With the just-in-time pattern, it doesn’t matter whether an element existed at page load or was added dynamically five minutes later. The pageswap 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.

The one thing to watch out for: make sure your data-id attributes (or whatever you’re using to match elements) are unique across all loaded batches. If your API returns items with IDs and you’re using those for the view-transition-name, you’re already fine. If you’re generating IDs client-side, make sure they don’t collide when new batches load.

Don’t Make People Sick

/* 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;
  }
}

This isn’t a nice-to-have. I need to be blunt about that.

People with vestibular disorders — and there are a lot more of them than most developers realize — can get physically nauseous from unexpected motion on screen. Not “mildly annoyed.” Nauseous. Dizzy. Migraines that last hours. The prefers-reduced-motion media query exists because real people checked a box in their OS settings that says “please stop making me sick.” Ignoring it is the accessibility equivalent of removing a wheelchair ramp because stairs look cleaner.

The @view-transition opt-in can stay outside the media query. That’s fine, it just tells the browser, “I want cross-document transitions enabled.” The browser will still do an instant cut between pages, which is visually identical to a normal navigation. It’s the animation customizations that need to be gated: the durations, the easing curves, the custom keyframes. Wrap all of that in prefers-reduced-motion: no-preference and you’re covered.

That prefers-reduced-motion: reduce block at the bottom is a belt-and-suspenders thing. Even if you miss wrapping some animation rule, forcing animation-duration: 0s on all the transition pseudo-elements ensures nothing actually moves. The !important is ugly but justified here. you genuinely want this to override everything, no exceptions.

You already saw the conditional opt-in pattern back in Part 1:

/* You can also just disable transitions entirely for reduced-motion users */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}

Either approach works. Wrapping the whole @view-transition rule means the browser won’t even attempt the transition – it’s a normal navigation, full stop. Keeping @view-transition active but killing the animation durations means the transition technically fires but completes instantly, which can matter if you have pagereveal logic that depends on event.viewTransition existing. Pick whichever fits your setup. Just don’t ship animated transitions without checking.

A thing worth considering here: “reduced motion” doesn’t necessarily mean “no motion.” 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.

@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;
  }
}

This is a judgment call. A fast, subtle cross-fade is less likely to trigger symptoms than a 400ms morphing animation with easing curves. But the safest option is always zero motion, and if you’re not sure, go with animation-duration: 0s. You can always add a gentler alternative later once you’ve tested it with actual users who rely on the setting.

Handle Old Browsers (By Doing Basically Nothing)

/* Feature detection, if you need it */
@supports (view-transition-name: none) {
  .card {
    /* maybe you want contain: paint for better snapshotting */
    contain: paint;
  }
}
// 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.

Here’s the thing though: you probably don’t even need that @supports check.

View transitions are progressive enhancement in the purest sense of the term. If a browser doesn’t understand @view-transition { navigation: auto; }, it ignores the rule. That’s 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’s fine.

Nothing breaks. No JavaScript errors. No layout shifts. No fallback code to write. The view-transition-name properties get ignored. The ::view-transition-* pseudo-element selectors match nothing. Your pageswap and pagereveal event listeners either don’t fire or event.viewTransition is null and your guard clause returns early. The whole feature is designed to be invisible when it’s absent.

That’s the beauty of this API, honestly. It’s one of the rare web platform features where you don’t have to write a single line of fallback code. Firefox doesn’t support it yet? Fine — Firefox users get normal navigation. Safari’s working on it but hasn’t shipped? Cool, Safari users click links and pages load. Nobody gets an error. Nobody gets a broken layout. Nobody loses anything. They just don’t get the fancy animation, and most of them will never notice it was supposed to be there.

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.

The only time @supports matters is if you’re adding styles that only make sense in the context of view transitions — like contain: paint on elements to improve snapshot quality, or hiding some loading state that the transition would normally cover. Gate those behind @supports (view-transition-name: none) so non-supporting browsers don’t get the side effects without the payoff.

Failure is invisible. That’s the whole point.

Ship It

Look, I’ve been building websites for a long time, and there’s 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’re maintaining a small aircraft carrier just so a card can animate into a hero image.

That trade-off is dissolving.

Cross-document view transitions let an <a href> 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’s not a small thing – it changes which projects need a framework and which ones just assumed they did.

The spec is young. It’s Chromium-only right now. The rough edges are real – you’ve seen them across both parts of this series. But the API is designed so well that when it’s not supported, nothing breaks. Your site just works the way sites have always worked. And when it is supported, it feels like magic that came free.

Here’s a quick cheat sheet to take with you:

  • Opt in with CSS, not the deprecated meta tag: @view-transition { navigation: auto; }.
  • Both pages must opt in or no transition happens.
  • 4-second timeout starts at navigation, not at render – use pagereveal to catch TimeoutError.
  • Images stretch during transitions because pseudo-elements default to object-fit: fill – fix it with object-fit: cover on ::view-transition-old and ::view-transition-new.
  • view-transition-name = identity (unique per page), view-transition-class = styling hook (shared across elements).
  • Don’t name elements upfront – use pageswap and pagereveal to assign names just-in-time. But keep your pageswap logic fast — the browser gives you a narrow window (10-50ms) before snapshots.
  • Clean up names after viewTransition.finished resolves to avoid stale conflicts.
  • Gate animations behind prefers-reduced-motion: no-preference — this is not optional.
  • Progressive enhancement is built in — unsupported browsers just get normal page loads

The best animations are the ones you don’t have to maintain a framework to get.

Cross-Document View Transitions Series

  1. The Gotchas Nobody Mentions
  2. Scaling View Transitions Across Hundreds of Elements (You are here!)

Cross-Document View Transitions: Scaling Across Hundreds of Elements originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.



from CSS-Tricks https://ift.tt/wDk1J3A
via IFTTT

Sunday, May 24, 2026

Mostly Clear today!



With a high of F and a low of 53F. Currently, it's 69F and Fair outside.

Current wind speeds: 7 from the Southeast

Pollen: 3

Sunrise: May 24, 2026 at 05:31PM

Sunset: May 25, 2026 at 08:06AM

UV index: 0

Humidity: 30%

via https://ift.tt/fS1to04

May 25, 2026 at 10:02AM

Showers today!

With a high of F and a low of 51F. Currently, it's 55F and Showers in the Vicinity outside. Current wind speeds: 0 from the Northwes...