> All in One 586

Ads

Monday, May 25, 2026

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

Saturday, May 23, 2026

Mostly Clear today!



With a high of F and a low of 50F. Currently, it's 58F and Clear outside.

Current wind speeds: 10 from the Southeast

Pollen: 4

Sunrise: May 23, 2026 at 05:32PM

Sunset: May 24, 2026 at 08:05AM

UV index: 0

Humidity: 54%

via https://ift.tt/cPoXl2b

May 24, 2026 at 10:02AM

Friday, May 22, 2026

Cloudy today!



With a high of F and a low of 44F. Currently, it's 55F and Mostly Cloudy outside.

Current wind speeds: 5 from the Northeast

Pollen: 4

Sunrise: May 22, 2026 at 05:32PM

Sunset: May 23, 2026 at 08:04AM

UV index: 0

Humidity: 47%

via https://ift.tt/QV1Amrp

May 23, 2026 at 10:02AM

The State of CSS Centering in 2026

What? Another article about centering?! But all we have to do is use display: flex | grid, then align-items: center. No, it’s align-content… wait… I think it’s justify-content. Well, let’s use margin: auto, this one works all the time, right?

Despite the countless number of online resources (even CSS-Tricks has a full guide on it), it’s easy to get confused when trying to center an element, whether vertically, horizontally, or both). I am sure you will find something that works by googling or trying different combinations. But do you really understand why the code you picked works? Is it the right one for your use case? Because it really does depend and require consideration!

In this article, we will do a fresh exploration of centering in CSS, and hopefully, you will learn something new by the end of it.

I already master CSS centering. Should I skip this article?

Stay with me because we will explore hidden tricks and modern features that you may not know — safe centering, text-box, centering in anchor positioning, etc.

Is centering still hard?

No, centering is not hard. Considering all the different and various ways to center an element, it’s an easy task that generally requires two or three lines of code. But, how many ways do we have to center an element? I did the count, and I was able to enumerate 100 different ways to center an element vertically and horizontally within a container.

Are you serious,100 ways?! That’s insane.

Yes, 100 is a ridiculously high number for what should be a simple task, but that number is misleading. If you check the list, you will find I marked about 60 of them in red, meaning they are hacky and not recommended. This leaves us with roughly 30 valid approaches. And within those valid options, many are basically the same, only written differently, so we can consider them redundant.

At the end of the day, the number of “unique” and “valid” ways to center an element is less than 15 (or even 10) but it was a fun exercise enumerating the different codes that can center an element. Go check the full list, you may learn something new!

Let’s look at things from a beginner’s perspective. For me, who has been writing CSS day and night for years, it’s easy to say “centering is not hard,” but what about to a newcomer who reads this and confronted with all those different ways to center stuff? Nah, it’s not easy at all. align-items, align-content, justify-content, place-self, margin: auto. What the hell?!

Too many properties for a task that everyone claims is easy! Well, let’s pick a code that works and move on. After all, if the item is in the center, then it’s fine, right? Let’s avoid making a lot of noise around this, or the CSS fanatics will shout at me.

Don’t think that way! Centering can be hard, and that’s fine. It doesn’t mean you are stupid. It simply means you need to understand how it works.

Don’t skip the important step of “learning” (like many do); otherwise you will find yourself doing a lot of copy/paste without really understanding what is going on. Sometimes it works, but sometimes it doesn’t, and it can be very frustrating.

Learn how to align before how to center

Centering is nothing but a special case of alignment in CSS, and alignment is a complex world. It’s not only left, center, right, or top, center, bottom. It’s more than that. The good news is that you can easily learn it. For this purpose, I wrote a deep dive I called “The fundamentals of alignment in CSS.”

It’s probably one of my longest writings, but believe me, it’s worth your time (and effort). I explain how alignment works in all the different CSS layout methods. It starts with understanding the alignment theory, which has two levels of alignment (“content” and “item”) and two axes (horizontal and vertical).

Diagram showing that place-content equals align-content plus justify-content, place-self equals align-self plus justify-self, and place-items equals align-items plus justify-items, alongside a visual example of all three inside a white container and black border.

Identifying the “content” and the “item” in every layout is the key to understanding how everything works. I insist on “every layout” because assuming it works the same everywhere is a very common mistake.

Do yourself a favor and read that detailed article — you will thank me later! And once you understand the core concept of alignment, centering will become child’s play.

Should I use Flexbox or Grid?

I see a lot of people who always use the same method to center an element, whatever the situation. You have the CSS Grid team and the Flexbox team. While both work, I don’t advise you to think that way. Remember that the goal is to understand and avoid quick copy/paste approaches.

Study your layout and your requirements, then decide which method to use. Maybe your case requires position: absolute or a simple text-align: center. Flexbox or CSS Grid aren’t always mandatory for centering stuff, and there is no one way that’s better than another.

That said, if I have to pick something, I would consider the following codes. Each one for each type of layout.

.container { 
  display: block;
  align-content: center;
  justify-items: center; 
}
.container {
  display: grid;
  place-content: center;
}
.container {
  display: flex;
  flex-wrap: wrap;
  place-content: center;
}

Note: justify-items in the context of a block container is not supported by all the browsers. It’s Chrome-only for now, so consider using Chrome to see the following demos.

The properties are defined in one place (the container), and the methods are suitable for centering one or multiple items.

You won’t notice a difference when centering a single item. The three methods behave the same.

With multiple items, Flexbox behaves differently. It has a responsive behavior where the items are initially laid out horizontally and wrap when the container is narrowed. Resize the container and see what happens.

And with multiple items of different sizes, they all behave differently.

We started with three approaches that give us the same “visual” result when working with a single item, but upon adding more items, we can clearly see they are different. This difference is important as it shows that it’s not about picking a random code to center stuff. It’s about understanding how each code behaves in different situations, then picking the most suitable one. It’s wrong to assume that we can center the same way using Flexbox, CSS Grid, etc. All the methods are different and rely on different mechanisms, even if they give the same result in the context of one item.

This also explains why we technically have 100 ways to center stuff. We have different layout types, and each layout has its own alignment logic. But when the structure is reduced to one item inside a container, we have a lot of choices, and many methods may look identical even though they are not.

So, let me repeat myself: Study the alignment logic behind each code to know which one is suitable for your use case. Don’t blindly copy/paste a code that simply “works.”

What about centering text?

When centering “boxes,” we generally don’t have any issues if we apply the properties correctly. But once we start dealing with text, it can be tricky to perfectly center things vertically. You know the extra space above or below that you cannot really control and you have to use magic values for line-height or padding to rectify it.

We now have a new property that allows us to fix this: text-box. It trims the extra space based on your configuration.

In both boxes, I align the content in the center using a common code. Notice that the first box is not that good. The text seems to be off, even though I am using the CSS properties correctly.

It’s frustrating, right? For CSS, everything is perfectly centered, but for us, it’s not. why!?

It’s related to how the font is designed and the space reserved for each character. Adding a border around the text will make things clear.

Two examples of the word Text next to a red square. The first example is slightly off center due to line height and the second is perfect centered against the square due to removing extra line height.

As you can see, the “text box” is centered, but there is unwanted space inside it. I was able to remove that space using one line of code:

text-box: cap alphabetic;

Let’s try lowercase text without descenders or ascenders.

This time I am using slightly different keywords:

text-box: ex alphabetic;

…to remove the space for perfect centering

Two examples of the word awesome next to a red square. The first example is slightly off center due to line height and the second is perfect centered against the square due to removing extra line height.

The values look strange and unintuitive, but I have created a small generator where you can easily specify which space you want to trim and get the code in no time.

Highlighting the rendered line height of a text showing the space it adds to the content.

And if you want more detail on that feature, check Danny Schwarz’s “Two CSS Properties for Trimming Text Box Whitespace.”

Centering with CSS Anchor Positioning

In some cases, you may need to use absolute or fixed position, which means we are dealing with an out-of-flow element and a different alignment logic; hence, another centering technique.

The common way to do that is the classic top/left combined with translate:

left: 50%;
top: 50%;
transform: translate(-50%, -50%);

It works, and everyone is happy, but it’s not the suitable code to use. In 2026, I would consider that code hacky, and worth avoiding. It’s like creating layouts using float. That a was a valid approach until we got Flexbox and CSS Grid, which were intentionally designed for this sort for thing.

It’s the same thing with absolutely-positioned elements. Today, it’s better to rely on modern CSS features like this:

inset: 0;
place-self: center;

The inset property controls the “inset modified containing block” (IMCB) and place-self (the shorthand for justify-self and align-self) aligns the element inside the IMCB. I explain all those concepts in great detail in this article.

Where is anchor positioning in all of this?

Great question! Anchor positioning relies on absolute (or fixed) elements and has its own mechanism for controlling an element’s placement relative to its anchor. We are specifically dealing with centering, so we have to talk about a new value, anchor-center.

Let’s start with the following example:

I am placing the text box above the anchor using position-area: top. You can drag the anchor, and the text box will remain stuck to the top and centered.

Let’s update the alignment and use place-self: center.

The position looks a bit off at first glance, but if you drag the anchor and look closely, you will see the box centered within the top area.

A light blue label that says CSS is Awesome in the top center of a container that includes an anchor icon places at the center left of the container. The container includes dashed red lines that highlight the position of both items.

Centering is indeed not easy! It’s confusing if you don’t know in which area your element is centered. You will think that something is broken because your eyes might not see it as a centered element.

If you want to get back to the previous position, you can use this:

place-self: end anchor-center;

…or this:

align-self: end;
justify-self: anchor-center;

What’s happening here is that, vertically, we place the element at the end (the bottom), and horizontally, we consider the center of the anchor element. In other words, the anchor-center value is what makes the element follow the anchor when you drag it!

This means we have two different ways to use anchor positioning for centering: Either (1) center relative to the selected area using the center value, or (2) center relative to the anchor using the anchor-center value.

You will rarely need to use the anchor-center value in most cases because anchor positioning comes with area-specific default alignment. Setting position-area should be enough, but it’s good to know how to adjust the alignment and understand the difference between center and anchor-center.

If you want to explore alignment in anchor positioning, I have create an interactive demo that allows you to set the area, adjust the alignment, and see the result. There are 36 different positions you can set using position-area and five alignment values per axis.

The UI for an interactive demo that places a label that says CSS is Awesome around different sides and edges of an anchor icon with controls to change that position and generate the CSS code for it.

Safe and unsafe centering

You are probably wondering what safety has to do with centering, right? Don’t worry, centering doesn’t present security risks, per se, but it can be a risky thing for your content!

Take the following example:

I am using CSS Grid to center a red square within a container and we have two situations. The red square is smaller than the container (a classic situation), and the red square is bigger than the container (a less common situation).

In both situations, the red square remains centered, i.e., its center point matches the container’s center point. This is an unsafe centering approach, and yet it’s the default behavior of many centering methods.

Why is it unsafe? The content inside the container is overflowing from all sides, so if you decide to hide the overflow and add a scrollbar, some parts of the content cannot be reached, which is a form of data loss. In this case, the top and left parts are lost. That’s what I mean by unsafe.

Try scrolling the second container, and you will notice that you cannot see the red square’s top and left borders.

We can fix this by using safe alignment like this:

place-content: safe center;

Now, when an overflow occurs, the browser will shift the element to a “safer” position that displays the whole content in case we need to scroll. In other words, the browser prioritizes content visibility over centering (the exact opposite of an unsafe alignment).

I know what you’re probably thinking, and you shouldn’t be thinking that! Adding safe everywhere isn’t a good idea. Sometimes the unsafe behavior is actually what we want, so only consider safewhen you’re faced with content obstruction.

Let’s get back to the anchor positioning demo:

If you drag the anchor closer to the edges, the box is stopped by those edges (the containing block) and the default alignment is lost!

A label that says CSS is Awesome centered above an anchor icon that sits toward the left edge of a container.

In anchor positioning, the default behavior is safe alignment. If you don’t know about it, you may spend a lot of time trying to figure out why the element is not centered.

You can change that behavior using the unsafe keyword:

place-self: unsafe end unsafe anchor-center;

Or:

justify-self: unsafe anchor-center;
align-self: unsafe end;

Now, the browser allows the box to overflow the container. It will prioritize alignment over potential content loss due to the overflow.

And if you think it’s useless to work with an unsafe alignment in anchor positioning, then you are wrong. Here is one use case where I needed to switch to an unsafe alignment. We have a sticky header with a small icon next to the website title that you can hover to show a tooltip. The sticky header creates a containing block for the tooltip and, by default, prevents it from overflowing its boundary. I had to use an unsafe alignment to allow the overflow and keep the tooltip correctly placed.

I know it can be confusing, but you will rarely need to mess with safety. Keep using the default browser behavior, but remember you have the safe and unsafe values you can use to rectify a misalignment.

Conclusion

I hope that after this article you will see centering from a different angle. It’s not about picking a code that works, and you’re done. It’s about understanding how alignment works, considering your specific use case and layout, picking the appropriate code, and, more importantly, understanding why it works.


The State of CSS Centering in 2026 originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.



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

Thursday, May 21, 2026

Thunderstorms/Wind today!



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

Current wind speeds: 12 from the East

Pollen: 4

Sunrise: May 21, 2026 at 05:33PM

Sunset: May 22, 2026 at 08:03AM

UV index: 0

Humidity: 84%

via https://ift.tt/2nj7SHl

May 22, 2026 at 10:02AM

Wednesday, May 20, 2026

Rain Early today!



With a high of F and a low of 40F. Currently, it's 48F and Rain outside.

Current wind speeds: 11 from the Southeast

Pollen: 4

Sunrise: May 20, 2026 at 05:34PM

Sunset: May 21, 2026 at 08:02AM

UV index: 0

Humidity: 74%

via https://ift.tt/vGe0DPQ

May 21, 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...