SJ (Scott Jehl)

For Your Convenience, This CSS Will Self-Destruct

photo of scott jehl profile headshot

By Scott Jehl

As a web developer, I find that good performance, accessibility, and reliability is often acheived by playing defense against the many problems folks may encounter while using the web. The more I ask myself questions like, "what could go wrong here if JavaScript fails to fully load, or has a runtime error, or merely takes a while to load?," the more I find my sites are usable when those things inevitably happen. And frankly, continuing to improve and refine this practice is one of my favorite parts of this job.

Along those lines, here's a little defensive pattern that I have used recently and quite enjoy.

Let's say that a portion of your webpage is hidden when initially delivered, and revealed at a later time after it has loaded, perhaps even using an animation. There are a number of ways to implement this sort of pattern, and some particular brand-new ways are the ways that I'd recommend you consider trying first.

Let's assume for whatever reason though, that those particular new ways aren't the way you have things set up already, or they don't fit your use case for some reason. In those cases, we often see sites start by delivering something like the following, where some HTML is initially hidden with CSS:

<style>
.pre-fade { opacity: 0; }
</style>

<img class="pre-fade" src="/path/to/my-image.webp" alt="...">

That will indeed reliably work to hide the element. From there, sites will often use JavaScript to wait for some later time to manipulate the DOM and cause the element to be revealed.

And that part will indeed pretty reliably work too–on your machine, at least.

The Problem

The problem with this very-common pattern is that if JavaScript ever fails to load, or has a runtime error, or even just takes a very long time to finish loading, you run the risk of having content that is inaccessible to your user for a long time, or even permanently.

No non-essential visual effect is worth that risk.

A Solution

I have found that a simple adjustment in the way any risky "staging" styles are applied can remove the risk of a worst case scenario. It works by qualifying your initial style(s) with a self-destructing timer: that is, a CSS @keyframes animation.

Employing this approach, instead of giving that element an opacity: 0; style directly, I'll apply that rule via CSS keyframes and ensure it's only active for a limited time via its animation-duration.

<style>
@keyframes hideBriefly {
  0%, 100% { opacity: 0; } 
}
.pre-fade { animation: hideBriefly 2s; }
</style>

<img class="pre-fade" src="/path/to/my-image.webp" alt="...">

With this change, I've applied a risky style that will unapply on its own after 2 seconds, allowing the element to become visible and accessible if JavaScript fails to do its thing in time. If all goes well of course, the JavaScript will load quickly, and proceed as planned with its DOM manipulation to produce an effect.

A note: I find this approach works best if the JavaScript first checks if the element's computed opacity is still 0 before it proceeds, just to be sure it doesn't apply an animation after the element has already been revealed. Also, it's worth pointing out that the timing here is arbitrary. 2 seconds may seem like a short wait to allow all of a site's JavaScript to load (especially in 2025! Sigh.), but if this element is critical to your performance, you may not want to allow it to be hidden much longer than that: hiding it for too long may even be delaying critical performance metrics like LCP.

The Impact

In addition to ensuring access to content, this approach is likely to improve performance for percentiles of users with slower connection speeds and budget devices if you keep that animation time low and aggressive. For those folks, a site's JavaScript files can often take many seconds to load and execute, so it's great to have something like this ready to bail out of anything fancy in favor of giving them something usable as soon as possible.

Anyway, that's Self-Destructing CSS. Go play some defense out there on behalf of your users!