Fixing the Flash: Why Animated Components Need preload.css
There is a failure mode specific to animated web components, and if you’ve used any custom-element library you have probably seen it. The page loads, your headline is fully visible for a split second, then it vanishes, then it plays its entrance animation. The animation itself is fine. The flash before it ruins the effect.
This article explains where that flash comes from, and what the
Where the flash comes from
A custom element like
So the timeline on a cold cache is:
- HTML is parsed. The headline renders, fully visible (wrong).
- The JS module arrives and runs. The component upgrades.
- The animation sets its “from” state. The headline snaps invisible.
- The spring runs. The headline animates in (finally right).
Steps 1–3 are the flash. It is a cousin of FOUC: a flash of un-animated content. It gets worse exactly when you can least afford it: on slow networks, with
The fix is a pseudo-class
CSS can target the not-yet-upgraded window directly.
Now the headline renders already invisible, the component upgrades, and the spring animates it in. The “from” state was never wrong, so there is nothing to flash.
The three shapes of hiding
The interesting part of the file is that not every component is treated the same way, because they own different things.
Components that animate themselves get
Components that animate their children hide the children instead:
A dialog’s content shouldn’t just be transparent before upgrade. It shouldn’t be in the layout at all. Otherwise the page height would jump when the component upgrades and moves its content into the native
Equally deliberate is what’s not in the file:
The text-layout components (
Wiring it up
The one rule: the CSS must be there before first paint. That means the
Via CDN:
Bundlers can import the same rules as a string and inline them, which saves a round trip. This site does that in its own layout:
The honest tradeoff
Hiding content until JavaScript arrives means that if the JavaScript never arrives (blocked script, extension, broken deploy), the hidden content stays hidden. That is the standard bargain of entrance animation on the web, not something unique to this library. But you should make the bargain consciously. Two mitigations worth knowing:
-
A
<noscript> override restores everything for users with JS disabled outright:With JavaScript off, the elements never upgrade, so
:not(:defined) keeps matching forever. The override flips everything preload.css hid (hosts and children both) back to visible.motion-dialog staysdisplay: none , which is right: a dialog can’t open without JavaScript anyway. -
Scope your exposure. Only above-the-fold entrance components risk a visible flash window. Content revealed on scroll, like a
motion-reveal far down the page, upgrades long before the user reaches it.
The flip side of the bargain: because the hidden state is plain CSS keyed to upgrade timing, the fix costs zero JavaScript, causes zero layout shift, and works identically with SSR, static HTML, or any framework.
Related
- motion-reveal · motion-stagger · motion-dialog: the components doing the hiding
- Recipe: An Animated Hero Section: where getting this wrong is most visible