Skip to content

Instantly share code, notes, and snippets.

@bkardell
Last active December 16, 2020 19:02
Show Gist options
  • Save bkardell/5583179826456ff799433c50b09410f1 to your computer and use it in GitHub Desktop.
Save bkardell/5583179826456ff799433c50b09410f1 to your computer and use it in GitHub Desktop.

Overview: Custom elements introduce what is effectively an "indeterminite" state about whether an element has a definition, which is resolved asynchronously... maybe. CSS currently doesn't offer expressive power beyond 2 boolean states via the :defined pseudo class. This is problematic because there is no good way for authors to create components which are both resilient to failure and provide a good user experience. This is inadequate because elements are by default not defined and we have no idea if or when they will even try to be defined, or, much worse, that such a thing failed.

Discussing with several developers, what we would like is the ability to delaratively handle this appropriately such that:

  • if script is disabled, authors can style something
  • if script is enabled, authors can style this in order to hide something visually/reserve space
  • if a known attempt to define fails, authors can style this and not just leave a hidden space
  • if a certain amount of time passes and no attempt to define is even made, authors can style style this and not just leave a hidden space

@jonathanneal made this handy diagram which attempts to visualize the states/desires, if that is easier to understand

Unmet, but basic Custom Elements case: Failures and delays described

A common example of this is that several elements I have seen are developed pretty ideally: Using PE, composition over inheritance (doesn't violate LSP) and so on, and then use :not(:defined) {visibility: hidden;} to prevent a FOUC, and the result is that all of that resilence is out the window. If scrtipt is disabled, their fallback content isn't shown. If a netowrk call fails and their element is not defined, no content is ever shown, etc.

I would like to suggest that there are several use case/wants here and that we lack the powers to handle them to give authors the ability to create things with a good user experience.

Examples

"Failure-insensitive"

Jon would like to include an element in his page:

<next-article>  
  <a href-"..."></a>
<next-article>

Jon's component thought about PE. In an ideal world, this component will be defined and complete work before the author sees it, and it will load the content of the next page in the background and make it available to the user fast and through some pleasant experience. This helps him paint the main thing the user wants on the screen fast, but also give them more content quickly, or maybe even control when that initiaties work to update the page automatically via IntersectionObserver, or something. Jon would prefer that the page have a moment to load before showing, so that someone doesn't click the link while it's still upgrading or see it change in most cases -- however, it's not critical. What matters most to Jon though is that his users not have to wait too long for content.

What he wants is a model more like fonts: Hide it for some time, but it's ok to give up and let it be shown because he thinks that is the right tradeoff for his user.

Jon would also like to be able to check whether he's hit this limit during definition so that he can decide to not upgrade if he chooses (again, like fonts)

"Fail-sensitive"

Lajja would like to write a <my-calendar> component which uses the users' login to locate their calendar and present information in the page about today's events. They have no fallback, but assume that the calendar will load and put a loading indicator inside. Frank is a user of Lajja's service who commutes on a bus and loses connectivity, or has very slow connections sometimes. Frank doesn't want to be told it's loading if it isn't because the network failed to load the definition in the first place. Frank is also happy to wait for content in these situations as long as it is still trying, but if the network drops and the element will never be defined, he would love to know that! Lajja's has another user, Hanna who likes to disable JavaScript by default and turns it on selectively if she really wants that content (her browser makes this easy). Hana is used to a lesser experience, and she's fine with that - but she doesn't like to not know there is content she could get, because it doesn't show up, nor to be lied to that something is loading forever when it isn't either. What can Lajja do to make her users happy?

What Lajja wants to do to make her users happy is to be able to know when we know that there has been an attempt to define, and that it failed, for whatever reason. This would let her show Hanna some notification when she has JavaScript disabled, it would let Frank know when we actually failed, but let him know we're trying.

Note that this case also applies to some of Lajja's "simpler" PE-like cases like trying to decorate a stateful interactive element simply to pretty it up, like

<x-slider>
 <input type="range">
</x-slider>

While this is potentially non-critical, as long as there is an attempt underway to define it, it is arguably better to not show the fallback content... Not only because there is FOUC, but because this creates potentially complex situations if the user begins interacting with it. If such an attempt fails, however, we'd like to show the still functional component.

What's missing

What authors could really use is a simple declarative way to style these use cases

  • There is no script support, I can style that case
  • There is script support, but it is not known about yet, I can style that case
  • There is a script that is attempting to load a definition for this
  • An error occurred while trying to load the definition, so it is unknown and wont be known, and I can style that failure case
  • Yay, it is defined.

But in the platform today, there's no way to map that out easily without developing your own infrastructure to map the definitions to scripts and manage special attributes or classses representing those things and twiddling a value on root or something.

There are many, many ways to solve this but an easy way to think about it is that what you'd like is the ability to express that some script intends to provide definitions for some elements, and for this to make information available via CSS to respond when that happens to not be the ccase.

In previous employment and projects since 2014, in cases where I had control over more of the base, I developed a few patterns to address this, but I'm more interested in agreement that there is a problem than suggesting any specific solutions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment