> All in One 586

Ads

Friday, May 22, 2026

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

Stack Overflow: When We Stop Asking

Let’s play a quick game: I’ll show a graph and try to guess what it’s about.

A line chart shaped like a giant bell curve that grows expontionally between 2009 and 2016 then sharply declines over the next ten years.
Source: Data Stack Exchange

No, it isn’t a crypto coin crashing a few hours after being minted. And not, it is also not an oscillatory/wavy graph made with pure CSS, but a harsher truth.

I already gave it away with the title, but it still hits like a ton of bricks to know it is the steep decline in the number of questions asked on Stack Overflow. You can see its peak around 2014 with more than 200,000 questions asked in a single month. But now in 2026, it is struggling to even hit 3,000 questions a month.

We don’t have to be experts in the field to find out the culprit. You guessed, it’s AI… mostly.

While AI is painted as the Stack Overflow killer, the truth is Stack Overflow’s downfall started long before ChatGPT’s release in late 2022.

A line chart shaped like a giant bell curve that grows expontionally between 2009 and 2016 then sharply declines over the next ten years. The chart is labelled to show the various peaks and valleys the timeline.

By community accounts and also from personal experience, moderation since its peak in 2014 has been (and still is) one of the leading causes for the lack of questions.

As the site grew, Stack Overflow needed a better way to moderate the hundreds of thousands of questions asked every month: the inevitable wall that forum-based communities hit when they scale beyond a certain point. There are several ways to try to solve this, but the route Stack Overflow took might not have been the best:

On Stack Overflow, we close or delete questions that can’t be answered straight away – it’s not very sociable, but it scales wonderfully.

It’s clear Stack Overflow wasn’t focusing on the quantity of the questions but rather on the quality of them, while avoiding duplicates as much as possible. This pattern was in favor of Google searches for questions that were already answered and, hence, living on pre-answered questions instead of on users making new or duplicate ones.

It wasn’t helpful either how the community seemed to close upon itself, making it harder for beginners to even ask a question. And if you’re like me, you probably want to inquire without being told you’re stupid, as if getting punished for wanting to learn.

Generative AI was the final nail in the coffin. I can’t complain about this, as AI seemingly provides the same answers without judgment (in fact, maybe too much encouragement) nor delay, so I can see why people might prefer asking an LLM instead.

However, as I dug deeper into this, my concern was no longer about just Stack Overflow, but the tech ecosystem at large. Questions like, are we still asking questions? Are we still seeking to be better? Or do we all rely on LLMs, and solely on LLMs, for advice? That kept ringing in my mind as I continued my research.

I believe that, beyond the fall of Stack Overflow, those questions linger more than ever. How AI has generally impacted our workflow, how we can use it in problem-solving, and what we can do about this as developers.

Problem-Solving and AI

Is AI a better programmer than you? What makes a programmer better than others is as subjective as it gets, but some are eager to say that AI can write code better than you. According to that research:

AlphaCode achieves human-level problem solving skills and code writing ability as shown by performance in programming competitions.

At least that’s when it was tested against Codeforce’s (an online code competition site) problems, where I admit it can and will perform better than your average programmer. But most developers don’t care about Contest problems beyond a technical interview; they know being a software developer is so much more than that.

AI writing quality code is an extremely nuanced topic and lacks a decisive conclusion. However, if you take the time to research, you’ll find that AI-generated code has lots of flagrant differences. According to the research from Cornell:

AI-generated code is generally simpler and more repetitive, yet more prone to unused constructs and hardcoded debugging, while human-written code exhibits greater structural complexity and a higher concentration of maintainability issues.

Okay, so it can generate simple code, but can it write good code? Even solve problems better than a software engineer would?

According to MIT research, AI can write good code, but it cannot possibly think and make decisions like a software engineer. AI cannot compete on that level yet, at least without running into a lot of bugs.

Drawing on both first-hand experience and feedback, if all you do is copy-and-paste AI-generated code without careful consideration, you are bound to hit serious bugs and possibly even vulnerabilities. In fact, VeraCode published an article stating that “[…] 45% of AI-generated code contains security flaws,” after testing for security vulnerabilities in 100 AI models. That’s a large percentage of code that’s flawed security-wise and would have cost implications for any user who wants to “vibe-code” without doing thorough checks.

Fun fact: GitHub released the results of its AI in software development survey in August 2024, and over 97% of its respondents have used AI outside or inside their work. That’s even aside from the companies enforcing the use of AI in your current code workflow. It’s literally everywhere; there’s almost no escaping its usage

But, does that mean it’s all bad? The answer to that, in my opinion, is no. According to research done by Harvard Business Review, AI is effective for helping solve problems (let’s not also ignore the trade-off from the study that AI workflows result in less motivation). In essence, it is perhaps best used to enhance problem-solving effectiveness.

This means that, as AI is taking over industries and being incorporated into our daily work, it still won’t replace your creativity and problem-solving approach, which you would need to tackle unique everyday challenges. It’s difficult to replicate.

Like every other tool, AI has its limits, and without human craftsmanship behind it, the tool is almost useless. A good craftsman uses all the tools at his disposal to achieve his goals, AI being just one of them.

“The effectiveness of the tool is determined by the skill of the craftsman who created it and the ingenuity with which he utilizes it.”

Craig D. Lounsbrough

The big danger is not just security vulnerabilities, but over-dependence on the tool, which I believe will lead to an eventual decline in the number of code craftsmen in the coming generation. How should newer and experienced developers go about this?

Some Advice

Here is a list of questions I ask myself when picking up AI in my development work:

  1. Am I asking the LLM smaller, specific questions? This way, I can verify each process step-by-step rather than eyeballing the whole system code as a whole. I’m still a developer in the sense that I am not leaving the LLM to do all the work.
  2. Am I evaluating the output when it’s finished? In other words, do I understand what it did? Would I be comfortable modifying the generated code if I know a better approach, or when I have to maintain it in the future?
  3. Am I checking the tool’s references? This may be more geared towards research instead of straight code output. Where exactly are its answers coming from? Are those good sources? Are there others? It’s important to know the tool is not citing a fictional source, but rather, coming up with modern and tried-and-true approaches.
  4. Have I tested the work? Did the tool understand the task and consider all edge cases? This is perhaps the most important question because knowing how people use your application is something a machine is less inclined to know than a human.

What happens when we stop asking?

Think about this: if we stop asking questions, how will AI be trained in the future? Technologies change and improve over time. What’s updated now will soon become old-fashioned. Take CSS, for example. With the recent CSS updates (nesting, view transitions, container queries, etc.), we are writing CSS vastly different than even a few short years ago. You wouldn’t want to be stuck with an outdated and clumsy solution trained from code written decades ago. If we stop asking questions and answering them, don’t you think that would make the LLMs lag behind? That’s just me speculating, but I think it’s easy to imagine that being the case.

We cannot deny Stack Overflow’s service over the years. It got us asking. It got us answering. It got us thinking. The question we should all ask ourselves is,Will LLMs do the same?

I’ll leave you with this quote from Stack Overflow co-founder Jeff Atwood:

Stack Overflow is you. This is the scary part, the great leap of faith that Stack Overflow is predicated on: trusting your fellow programmers. The programmers who choose to participate in Stack Overflow are the “secret sauce” that makes it work.


Stack Overflow: When We Stop Asking originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.



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

Tuesday, May 19, 2026

Showers today!



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

Current wind speeds: 7 from the East

Pollen: 3

Sunrise: May 19, 2026 at 05:34PM

Sunset: May 20, 2026 at 08:01AM

UV index: 0

Humidity: 58%

via https://ift.tt/yWKhCuJ

May 20, 2026 at 10:02AM

Monday, May 18, 2026

Mostly Cloudy/Wind today!



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

Current wind speeds: 19 from the North

Pollen: 4

Sunrise: May 18, 2026 at 05:35PM

Sunset: May 19, 2026 at 08:01AM

UV index: 0

Humidity: 81%

via https://ift.tt/IM1JAp6

May 19, 2026 at 10:02AM

Cross-Document View Transitions: The Gotchas Nobody Mentions

I wasted an entire Saturday on this.

Not a lazy Saturday either, but one of those rare, carved-out, “I’m finally going to build that thing” Saturdays. I’d seen Jake Archibald’s demos. I’d watched the Chrome Dev Summit talk. I knew cross-document view transitions were real, that you could get those slick native-feeling page transitions on plain old multi-page sites without a single framework. No React. No Astro. No client-side router pretending your multi-page application (MPA) is single-page application (SPA). Just HTML pages linking to other HTML pages, with the browser handling the animation between them. Hell yes.

So I started building. And nothing worked.

The first tutorial I found had me dropping <meta name="view-transition" content="same-origin"> into my <head>. Seemed simple enough. I added it to both pages, clicked my link, and… nothing. No transition. No error. Just a normal, instant page load like it was 2004. I opened DevTools, double-checked my syntax, restarted the server, tried Chrome Canary, cleared the cache. Nothing. I did what any self-respecting developer does at that point – I copied the code character by character from the blog post and pasted it in. Still nothing.

I spent two hours convinced I was an idiot.

Turns out that <meta> tag syntax? Deprecated. Gone. Chrome shipped it, then replaced it with a CSS-based opt-in, and half the internet’s tutorials still show the old way. Those older blog posts still rank well. They look authoritative. And they’re just wrong now. Not wrong because the authors were bad – wrong because the spec moved under everyone’s feet and nobody went back to update their posts.

The other half of the tutorials I found were about same-document view transitions. SPA stuff. document.startViewTransition() called in JavaScript when you swap DOM content yourself, which is cool and useful but a completely different feature when you actually sit down to implement it. The API surface is different. The mental model is different. The gotchas are very different. And yet, Google “view transitions tutorial” and good luck figuring out which flavor you’re reading about until you’re three paragraphs deep.

So if you’re here, I’m guessing you’ve been through some version of this. You tried the meta tag. It didn’t work. You tried the JavaScript API on a real multi-page site and realized it only fires within a single document. You maybe got something half-working in a demo but it fell apart the second you added real content — images stretching weird, transitions hanging for seconds with no explanation, or your CSS file turning into 200 lines of view-transition-name declarations because you have a grid of 40 product cards. You blamed yourself. It wasn’t your fault. The documentation ecosystem around this feature is a mess right now, and the spec has been a moving target.

This is Part 1 of a two-part series, and it’s the article I wish existed on that Saturday. We’re going to cover the actual current way to opt in with @view-transition in CSS (not the meta tag, not JavaScript), then dig into the 4-second timeout that will silently kill your transitions on slow pages and how to debug it, then fix the aspect ratio warping that makes every image-heavy transition look like a fun house mirror, and finally get a proper handle on the pagereveal and pageswap events that give you programmatic control over the whole lifecycle.

In Part 2, we’ll tackle the scaling problem – how to handle view-transition-name across dozens or hundreds of elements without your stylesheet becoming a disaster, the difference between view-transition-name and view-transition-class, just-in-time naming patterns, and doing prefers-reduced-motion the right way.

Cross-Document View Transitions Series

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

Grab coffee. Maybe a refill. This one’s dense and I’m not going to waste your time, but there’s a lot of ground here and none of it is obvious.

The Old Way is Dead

<!-- THIS IS DEPRECATED - stop copying this from old tutorials -->
<meta name="view-transition" content="same-origin">
/* THIS is the current opt-in - goes in your CSS */
@view-transition {
  navigation: auto;
}

Here’s the minimal setup. Two HTML files, one CSS rule on each. Note that, as of 2026, cross-document view transitions are supported in Chromium-based browsers and Safari 18.2+. Firefox support is in progress as I’m writing this.

That’s it. Two HTML files. One CSS rule on each. Click a link between them in a supporting browser (like modern Chromium or Safari 18.2+) and you get a smooth cross-fade. No JavaScript. No meta tags. No build step. The browser snapshots the old page, snapshots the new page, and animates between them automatically.

Now, why did the spec move from a meta tag to a CSS at-rule? It wasn’t arbitrary.

The meta tag was a blunt instrument. It was on or off for the entire page. You couldn’t say “enable transitions on desktop but not on mobile where the animations feel janky on low-end hardware.” You couldn’t conditionally opt in based on user preferences. It was just… there, or not.

The CSS approach opens all of that up:

/* Only enable transitions if the user hasn't asked for reduced motion */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}
/* Only enable on viewports wide enough for the animation to feel good */
@media (min-width: 768px) {
  @view-transition {
    navigation: auto;
  }
}

That’s a real upgrade. You get the same conditional power you already have with every other CSS feature. Media queries, @supports, whatever scoping logic you want — it all just works because the opt-in lives where your styles live.

There’s also a subtlety that matters: the CSS rule can be different on the old page versus the new page. Both pages need to opt in for the transition to fire. If Page A has @view-transition { navigation: auto; } but Page B doesn’t, you get no transition. This is actually useful – it means your 404 page or your login redirect can skip transitions without any JavaScript coordination.

One more thing worth noting here: navigation: auto only kicks in for user-initiated, same-origin navigations. If the user clicks a regular link or hits the browser’s Back button, you get a transition. But window.location.href = "/somewhere" set programmatically, or a cross-origin link, or a form submission with a POST? No transition. The browser is intentionally conservative about when it fires, and honestly that’s the right call. You don’t want a fancy cross-fade on a POST request that’s creating a payment.

Look, if you’ve been following an outdated tutorial and your transitions just silently don’t work, this is almost certainly why. The meta tag shipped in Chrome 111, got a few months of real-world use, and then the Chrome team deprecated it in favor of the CSS at-rule starting around Chrome 126. No console warning. No error. The old syntax just quietly does nothing now. Honestly, a deprecation warning in DevTools would’ve saved me (and probably you) a lot of grief, but here we are.

Swap the meta tag for the CSS rule. That’s step one. Everything else in this article builds on it.

Your Transition Will Randomly Die, and Here’s Why

// Drop this in your pages to see what's actually happening
window.addEventListener("pagereveal", (event) => {
  if (!event.viewTransition) {
    console.log(
      "No view transition - page didn't opt in or browser skipped it",
    );
    return;
  } // This is the one that'll save your sanity

  event.viewTransition.finished
    .then(() => console.log("Transition completed ✅"))
    .catch((err) => {
      // You'll see "TimeoutError" here and nowhere else
      console.error("Transition killed:", err.name, err.message);
    });
});

Here’s the thing nobody puts in their blog post: cross-document view transitions have a hard 4-second timeout. If the new page doesn’t reach a state the browser considers “renderable” within 4 seconds of the navigation starting, the transition just… dies. No animation. No cross-fade. The new page snaps in like view transitions don’t exist. And unless you’ve got that pagereveal listener wired up and your console open, you won’t get any indication that anything went wrong.

Four seconds sounds generous — until it isn’t.

Think about what happens on a real site. Your page loads. The HTML arrives, fine, that’s fast. But maybe you’ve got a big hero image that’s render-blocking. Maybe there’s a slow API call that your server waits on before sending the response – a product page hitting an inventory service, a dashboard waiting on analytics data, anything with server-side rendering that actually does work before responding. Maybe you’re on a decent connection but the page has three web fonts loading from Google Fonts with font-display: block. Any of these can push you past that 4-second window, and the timeout doesn’t care why you’re slow. It just cuts the transition.

The really maddening part? It works perfectly on localhost. Your dev server responds in 80ms. The transition is butter. You deploy to production, your server’s cold-starting a lambda or your CDN cache missed, and suddenly users get zero transitions on the first click. You can’t reproduce it locally. You start questioning everything.

// You can also catch this on the OLD page using `pageswap`
// Useful for cleanup or logging which navigations fail
window.addEventListener("pageswap", (event) => {
  if (event.viewTransition) {
    event.viewTransition.finished.catch((err) => {
      // Log it, send it to your analytics, whatever
      console.warn("Outgoing transition aborted:", err.name);
    });
  }
});

So, what do you actually do about it?

Option one: make your page faster. I know, groundbreaking advice. But seriously – if your cross-document transition is dying, that’s a signal your page load is genuinely slow. The timeout is acting as a performance canary. Look at your Performance tab in DevTools, run a Lighthouse audit (which may not be perfect), figure out what’s blocking first render. This isn’t view-transition-specific advice, but the timeout forces you to care about it.

Option two is more interesting, and it’s the thing I wish I’d known about immediately.

<!-- Note: rel="expect" is newer and browser support is rolling out -->
<link rel="expect" href="#hero" blocking="render">

This:

<link rel="expect" href="#hero" blocking="render">

…tells the browser: “Don’t consider this page renderable until an element matching #hero is in the DOM.” That sounds like it would make things slower, and in a way it does – it delays first paint. But for view transitions, that’s exactly what you want: you’re telling the browser to hold the snapshot until the important content is actually there, rather than snapping a screenshot of a half-loaded page or, worse, timing out because some image in the footer is still downloading and blocking something.

It’s a trade-off. You’re choosing a slightly delayed, but smooth, transition over a fast, but-broken, one.

Honestly, the 4-second limit is probably the right call from the browser’s perspective. You don’t want a user clicking a link and staring at a frozen page for 10 seconds while the browser waits to do a fancy animation. At some point, just showing the damn page is better than a pretty transition. But I wish Chrome would surface the timeout more visibly – a DevTools warning, a performance marker, something. Right now it fails silently and that’s the whole problem.

One more thing worth knowing: the timeout clock starts when navigation begins, not when the new page’s HTML starts arriving. Network latency counts. The Time to First Byte (TTFB) Core Web Vital counts. If your server takes 2 seconds to respond and your page takes 2.5 seconds to render after that, you’re over the limit even though neither half feels slow on its own.

A debugging tip that’s saved me more than once: Chrome’s DevTools has an Animations panel (it’s under “More tools” if you don’t see it) that can actually capture view transitions in action. You can slow them down to 10% speed, replay them, and inspect the pseudo-element tree mid-animation. It’s not obvious that it works for view transitions, but it does. Between that and the pagereveal listener above, you can diagnose most timeout issues pretty quickly.

Put that pagereveal listener in early. Watch your console during testing. You’ll thank yourself later.

Why Your Images Look Like Taffy

This one’s easier to show with a same-document demo first (since you can actually run it in a single file), but the problem and the fix are identical for cross-document transitions.

Run that. Click the image. Watch the dog turn into silly putty.

The image itself has object-fit: cover on both sides. The thumbnail looks fine, the hero looks fine. But during the transition? The browser doesn’t transition your <img> element. It takes a screenshot of the old state, takes a screenshot of the new state, and morphs between them. Those screenshots are flat raster images. Your carefully applied object-fit? Gone. The browser is just scaling a bitmap from one box size to another, and when a 150×150 square gets stretched into a 600×300 rectangle, you get taffy.

Here’s the fix:

/* THE FIX - target the transition pseudo-elements directly */
::view-transition-old(hero-img),
::view-transition-new(hero-img) {
  /* Treat the snapshot like an image in a container - crop, don't stretch */
  object-fit: cover;
  overflow: hidden;
}

That’s the whole thing. Two properties on two pseudo-elements.

What’s actually happening: the browser generates a tree of pseudo-elements for every named transition. For an element with view-transition-name: hero-img, you get this structure during the animation:

::view-transition
└── ::view-transition-group(hero-img)
    ├── ::view-transition-old(hero-img)
    └── ::view-transition-new(hero-img)

The ::view-transition-group smoothly animates its width and height from the old dimensions to the new ones. That’s the morphing rectangle you see. Inside it, the old and new pseudo-elements hold the actual bitmap snapshots, and by default they’re set to object-fit: fill – meaning “stretch to fill whatever box you’re in, aspect ratio be damned.”

Switching to object-fit: cover tells those snapshots to maintain their aspect ratio and crop the overflow instead. Same mental model as a background image with background-size: cover. The transition still animates the box from square to rectangle (or whatever your shapes are), but the image inside crops gracefully instead of warping.

You could also use object-fit: contain here if you’d rather see the full image with letterboxing instead of cropping. It depends on what looks right for your content. But cover is what you’ll want 90% of the time, especially for product images and hero shots.

For cross-document transitions, the CSS is identical – you just put it in both pages’ stylesheets:

/* This works cross-document. Same selectors, same fix. */
/* Put it in your shared CSS file that both pages load. */
@view-transition {
  navigation: auto;
}

::view-transition-old(hero-img),
::view-transition-new(hero-img) {
  object-fit: cover;
}

/* You can also control the animation timing on the group */
::view-transition-group(hero-img) {
  animation-duration: 0.4s;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

Honestly, I think object-fit: cover should be the default on these pseudo-elements instead of fill. I get why the spec chose fill – it’s predictable, it matches what object-fit defaults to on replaced elements everywhere else in CSS – but in practice, how often do you actually want a stretched bitmap during a transition? Almost never. You’ll be adding this override on basically every image transition you build.

One more variant that’s useful when the aspect ratios are wildly different – say a tall portrait thumbnail transitioning into a cinematic widescreen hero:

/* Fine-tune where the crop happens on each side of the transition */
::view-transition-group(hero-img) {
  overflow: hidden;
  border-radius: 8px; /* keep it pretty mid-flight */
}

::view-transition-old(hero-img) {
  object-fit: cover;
  object-position: center center;
}

::view-transition-new(hero-img) {
  object-fit: cover;
  object-position: center top; /* keep the top of the hero visible */
}

You can set different object-position values on old versus new, which lets you control where the crop happens on each side of the transition independently. The old thumbnail might look best cropped from center. The new hero might need to anchor to the top. Mix and match.

This took me an embarrassingly long time to figure out. The fix is two lines of CSS, but if you don’t know the pseudo-element tree exists, you don’t even know what to target. Now you do.

The Two Events That Tie it All Together

You’ve already seen pagereveal and pageswap show up in the code above, but let’s take a step back and talk about what they actually are. Understanding these two events is going to be important, because in Part 2 we’ll lean on them heavily for the just-in-time naming pattern that makes view transitions actually scale.

Cross-document view transitions happen across two pages that have no JavaScript connection to each other. Page A doesn’t know about Page B’s DOM. Since the old and new pages have no way to communicate directly, these events are your only way to coordinate the transition on both sides. Page B didn’t exist when Page A was running. So how do you coordinate anything? How do you decide which elements to name, or customize the transition based on where the user is heading?

That’s what these two events are for. They’re your hooks into the transition lifecycle, one on each side of the navigation.

pageswap fires on the outgoing page, right before it gets replaced. This is your last chance to touch the old page’s DOM before the browser snapshots it. The event gives you two key properties:

  • event.viewTransition: the ViewTransition object for this navigation, or null if no transition is happening.
  • event.activation: a NavigationActivation object that tells you where the user is going.

That activation property is the really useful one. event.activation.entry.url gives you the destination URL, and event.activation.navigationType tells you whether it’s a push, replace, traverse (back/forward), or reload. This means you can customize the outgoing side of the transition based on the destination. On a product listing page, for example, you can check which product the user clicked, find the matching card, and assign a view-transition-name to just that element right before the snapshot happens.

pagereveal fires on the incoming page, right after the page becomes active but while the transition is still running. This is your chance to set up the new side. The event gives you:

  • event.viewTransition: same deal, the ViewTransition object or null.

On the incoming page, you check where the user came from using navigation.activation.from.url (via the Navigation API), and you read the current URL from window.location. Between those two pieces of information, you know exactly what kind of navigation just happened and can set up the incoming page’s transition elements accordingly.

Here’s the full lifecycle in order:

  1. User clicks a link on Page A.
  2. pageswap fires on Page A. This is your window to name elements and customize outgoing state.
  3. Browser snapshots the old page (capturing any named elements).
  4. Navigation happens, new page loads.
  5. pagereveal fires on Page B. You can name elements, customize incoming state.
  6. Browser snapshots the new page.
  7. Transition animates between the two snapshots.
  8. viewTransition.finished resolves (or rejects) on both sides.

Three things to keep in mind with these events:

First, always guard with if (!event.viewTransition) return at the top of your handlers. pagereveal actually fires on every navigation – initial page load, back/forward, the works – not just view transitions. If there’s no transition happening, event.viewTransition will be null, and your handler should bail out gracefully. These handlers are transition sugar, not application logic. Never put side effects in them that you need for the page to work.

Second, pageswap only fires if the old page opted into view transitions and the navigation is same-origin. If the user middle-clicks to open in a new tab, or the navigation goes cross-origin, the event either won’t fire or event.viewTransition will be null. That’s fine, your guard clause handles it.

Third, and this is easy to overlook: both events give you access to viewTransition.finished, which is a promise that resolves when the transition completes or rejects if something goes wrong (like a timeout). Always use this for cleanup, as in removing view-transition-name values you set dynamically, resetting state, whatever. Stale names from a previous transition will ruin your next one.

We’ve been using these events lightly so far – a pagereveal listener to catch timeouts, a pageswap listener for logging. In Part 2 of this little series, they become the backbone of the whole scaling strategy. Stay tuned.

What’s Next

That covers the three gotchas that’ll bite you first: the deprecated meta tag that silently does nothing, the 4-second timeout that kills transitions without telling you, and the image distortion that turns every aspect ratio change into a fun house mirror. Plus the two events that give you hooks into the whole lifecycle.

In Part 2, we’ll tackle the scaling problem. When you’ve got a grid of 48 product cards and each one needs a unique view-transition-name, how do you keep your CSS from exploding? The answer involves view-transition-class (which is different from view-transition-name in ways that aren’t obvious), a just-in-time naming pattern using the pageswap and pagereveal events we just covered. And one critical note: we’ll cover prefers-reduced-motion in Part 2, but if you take nothing else from this series, take this: animations can literally make people physically nauseous. Always check that preference and respect it.

The gotchas are behind you. Now it’s time to make it scale.

Cross-Document View Transitions Series

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

Cross-Document View Transitions: The Gotchas Nobody Mentions originally handwritten and published with love on CSS-Tricks. You should really get the newsletter as well.



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

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-cont...