FOUCE Should Be the Browser's Problem
I've already written about FOUCE twice…it's that annoying Flash of Unstyled Custom Element that occurs when the page first loads. It's one of the most common complaints from folks who use web components.
And while the proposed solution works in practice, it's not exactly ergonomic:
- Cloak the page with a class and a CSS rule
- List every custom element by name
- Wait for all of them to register (and use a
setTimeout()as a fallback) - Remove the class with JS to reveal the page
Since we're a bit limited with what we can do in userland, I began pondering what browsers could do to solve such a problem.
What if we could register custom elements like this?*
<script type="element" src="my-button.js"></script>
When such a script is on the page, the browser would load the custom element from the module, wait until it fully resolves and registers, and then paint. That means zero FOUCE out of the box, and browsers could even take it a step further and guarantee those elements are available immediately, removing the need for await customElements.whenDefined().
Every custom element defined this way would become a first-class citizen, just like native HTML elements.
“But We Already Have blocking=render”
The blocking="render" attribute tells the browser to hold off painting until the script has been fetched and executed. If your file is a flat module that calls customElements.define() at the top level, this works today:
<script type="module" src="my-button.js" blocking="render"></script>
Alas, that's not how most component libraries are built. Registration tends to happen behind an await, e.g. a dynamic import or a lazy loader that defines elements as it finds them in the DOM. The module finishes executing before the elements are defined, so the browser paints and, well, we're right back where we started. 😕
The problem is that blocking=render cares about scripts, but FOUCE is about custom elements.
Changing the Spec
I know this isn't a fast or easy thing to change but, at the moment, every project that wants to avoid FOUCE has to discover the problem, find a solution, and wire it all up correctly. That's a lot to ask for something the browser could easily handle on its own, and it's a big reason people reach for SSR or, more commonly, decide against using web components.
I'm not married to this proposed syntax, but I do hope this inspires browser makers to give us a sensible way to prevent FOUCE. This would
The browser already knows when custom elements get defined, it just won't let us wait for them.
*I considered <link rel="element" href="my-button.js">, but since custom elements are defined in JavaScript where other potentially unrelated logic can execute, it's seems <script type="element"> is more appropriate.