Solving FOUC in Web Components

Thu Mar 10 2022 08:43:30 GMT+0000 (Coordinated Universal Time)

So Fellow Human, you wanna build with web components?

Solving a Common Web Component Issue: Flash of Unstyled Content

So, you are just starting out with web components and you’re super excited to test out the tech—you've been hearing lot about it.

You can finally rid yourself of any obligation to one framework and slap your custom designed components into your app...

but something’s wrong. When the page loads, you notice something: a jarring little glitch right as the content finishes loading.


What you are seeing is called a Flash of Unstyled Content (FOUC), and it is not unique to web components. It happens anytime the browser shows content before any styling has been applied.

You sigh in disbelief. Your shiny new web component is broken out of the box.

If you're like me, and this kind of subtle aesthetic detail is important to you, rest easy knowing that there is a pretty simple fix.

but first, what is the problem, exactly?

The Root of the Problem

We know that the Flash of Unstyled Content happens because there is a delay between when we are seeing the custom element on the screen and when the styling is being applied.

Depending on what styles you are expecting, this can end up being rather dramatic.

I had never seen this exact phenomenon, though, until working with web components.

What's the deal?

Using web components requires JavaScript to define the custom element, encapsulated logic, and styles that get injected into the web component's Shadow DOM.

So, when a browser requests your page, it starts to download and render the assets— html, styles, JavaScript— and then it runs the script where you define your custom elements using customElements.define(). Then, the Shadow DOM is injected into the element in the root DOM.

Unless you are dynamically adding your custom element to the DOM, you probably already registered your custom element somewhere on your page simply by adding it to the page like any other element.


But maybe you are using a static site generator and want an island-type architecture where you just have small sections of interactivity. You could use petite-vue or alpine.js, but you decide web components are a good fit, and I would agree.

The real problem is, your component is registered and showing up on the page, but any styling specific to the web component is not yet defined. Styling specific to the web component exists within the Shadow DOM, and it isn't available until you define the component in JS.

That is why there is a brief delay between when the component is loaded on the page and when the styling specific to the component is applied, which causes the undesirable flash.

The Fix

The fix is actually really simple. You just pre-style the registered component until it is defined, and then once it is defined, remove the styles.

If only there was a selector for undefined components....

There is! There is a psuedo-class :defined that we can pair with the pseudo-class :not() to end up with :not(:defined).

We add this to our custom element, along with any styles you want to use to hide the element. This can go in your global style.css file or the like.

my-custom-el:not(:defined) {
	display: inline-block;  
	height: 100vh;  
	opacity: 0;  
	transition: opacity 0.3s ease-in-out;

keep in mind that default opacity is 1, so by setting opacity to 0 in the :not(:defined) state and use the transition property, we create a fade transition.

After <my-custom-el> has been defined, the selector (my-custom-el:not(:defined)) no longer matches and the opacity changes to 1, making the defined component visible on the page.


Note that if the defined component styles include explicit height changes from the pre-styles, you will end up with a different type of glitch: layout shift. This one is also easily solvable if you know what the component height will be beforehand.

Aside: why don't I see FOUC in Vue.js?

So, we have our working, flashless web component. Sweet relief.

But the curious among you may wonder... why don't we see a similar thing happen in frameworks like Vue? I have worked with Vue 3 since its initial release a few years ago and have never seen FOUC happen.

Let's think about the sequence of components loading on the page. First, the browser request resources and starts downloading and rendering on the page. The body element contains the following:

	<div id="app"></div>
	<script src="bundle.js"/>

and doesn't yet contain any components inside your app.

Once the script with the bundle runs and loads your app, components start going through their respective lifecycles and get mounted to the page. As they do, their styles are already all available to them, unlike web components.

null, did you know that there is a way to define web components using Vue, and in this case, you would have potential for the original situation we remedied above?


So, we have seen that web components are fundamentally different in their mounting behaviour, and that they have to wait for JavaScript to run customElements.define() before encapsulated styles are applied along with the rest of the Shadow DOM. This can cause an ugly Flash of Unstyled Content (FOUC) that is easily remedied by pre-styling the registered component using :not(:defined) CSS pseudo-classes. Keep it easy, Fellow Human.

Update: Declarative Shadow DOM


x-foo:not(:defined) > template[shadowroot] ~ * {  
display: none;  //opacity: 0, whatever you like


I'm always open to thoughts on the subject matter, new ideas, relevant criticisms, and different ways of looking at things. Have one of these? Write it in the comments below.



Jacob Milhorn Dot Com Behind the Scenes


Behind the scenes view of how cool I look when I build websites. See how jacob milhorn dot com was made using 11ty static site generator, Vue single file components, and Slinkity to bring it all together.


Hey, I'm Jacob


I'm Jacob. TL;DR I transitioned from designing theoretical machines for advanced manufacturing processes in aerospace to designing concrete software solutions so that I could help real people with real problems.


Quit Hacking, Instead Build Up Mental Models


The more I invest in foundational knowledge, the more often I see the benefit in every area of expertise in which I am trying to grow.


Dynamic Imports: The real MVP of Islands Architecture


Without dynamic imports, Island architecture wouldn't make nearly as much sense, since you would be loading all JS right out of the gate. `import()` lets us load components precisely when we want to - a powerful mechanism that powers our Islands.


Solving FOUC in Web Components


Have you ever used Web Components and experienced some troublesome Flash of Unstyled Content (FOUC) as the page loads? This article presents a simple fix.