Debug panel

Close debug panel
Roma’s Unpolished Posts

A good way to hide non-defined custom elements

Published on:
Categories:
CSS 61, Custom Elements 4
Current music:
BimBamBoom — Shinzo BakuBaku Ochokochoi
Current drink:
Peppermint Tea

Introduction

This is a very short post about a thing that I have wanted to share for quite a while. I am pretty certain that someone already wrote about this at some point, but hey. There is so much content everywhere that it might be useful to repeat stuff, making it more likely someone will stumble upon a solution to their problem. And if you know other articles about this topic: please, let me know!

The Problem

Let me show the problem in an example (refresh the page if you want to see how the slow element is defined with a delay).

Fast: Fallback

Slow: Fallback

Undefined: Fallback

.example1 {
	& fast-element,
	& slow-element,
	& undefined-element {
		&:not(:defined) {
			/* Bad; don’t do this. */
			visibility: hidden;
		}
	}
}
<div class="example1">
	<p>
		Fast:
		<fast-element>Fallback</fast-element>
	</p>
	<p>
		Slow:
		<slow-element>Fallback</slow-element>
	</p>
	<p>
		Undefined:
		<undefined-element>Fallback</undefined-element>
	</p>
</div>
<script>
	class MyCustomElement extends HTMLElement {
		connectedCallback() {
			this.attachShadow({ mode: 'open' });
			this.shadowRoot.textContent = 'Defined!';
		}
	}
	customElements.define(
		'fast-element',
		MyCustomElement
	);
	setTimeout(() => {
		customElements.define(
			'slow-element',
			class extends MyCustomElement {}
		);
	}, 2000)
	// Nothing for the undefined one, duh.
</script>
There are three custom elements: one is defined right away, another — in two seconds, and the third is never defined.

All three have the same styles applied to them, and you can see how it works. Or, rather, it does not.

Without a fix, if an element is never defined, the fallback content will never be shown. This could happen for many different reasons: starting from any random JS error that prevents it from happening, and ending in a user having JS disabled in their browser. Or, in my case, just forgetting to define the element.

And when an issue like this happens — the element ends up being not visible and not accessible.

Animated Solution

One possible fix that I like: use an animation that would start from the element being hidden, and then, after some delay, it will reveal the fallback regardless (refresh the page to see the example in action):

Fast: Fallback

Slow: Fallback

Undefined: Fallback

@keyframes --not-defined {
	from { visibility: hidden }
}
.example2 {
	& fast-element,
	& slow-element,
	& undefined-element {
		&:not(:defined) {
			animation: --not-defined 0s 3s both;
		}
	}
}
<div class="example2">
	<p>
		Fast:
		<fast-element>Fallback</fast-element>
	</p>
	<p>
		Slow:
		<slow-element>Fallback</slow-element>
	</p>
	<p>
		Undefined:
		<undefined-element>Fallback</undefined-element>
	</p>
</div>
HTML here is the same as in the previous example, but we have a 3s grace period after which the non-defined element’s fallback is shown anyway.

I saw other approaches to that, like having something what detects if JS is enabled, but these kinds of approaches will not help with the JS error edge case.

Future Alternative @starting-style

In the future, when @starting-style will be more widely available, we could use it instead: the following example will work only in browsers that support it. And when they do boot support it, the styles won’t apply at all, which is also ok, as I always see this technique of hiding contents until defined as an enhancement, everything will still work without it, it just won’t be as pretty.

Fast: Fallback

Slow: Fallback

Undefined: Fallback

.example3 {
	& fast-element,
	& slow-element,
	& undefined-element {
		&:not(:defined) {
			transition: visibility 0s 3s;
			@starting-style {
				visibility: hidden;
			}
		}
	}
}
<div class="example3">
	<p>
		Fast:
		<fast-element>Fallback</fast-element>
	</p>
	<p>
		Slow:
		<slow-element>Fallback</slow-element>
	</p>
	<p>
		Undefined:
		<undefined-element>Fallback</undefined-element>
	</p>
</div>
Again, same HTML, and same behavior in the browsers that support @starting-style

Final Words

This case is a thing that I often see people not doing: not considering the failing states of something they implement.

It is always a useful habit: to ask yourself “What is the worst-case scenario?” when doing something as drastic as hiding content.

I am in no way an expert in custom elements. If you know any downsides of my approach or a better way to shove the same: please, let me know!

Please share your thoughts about this on Mastodon!