A Progressive Enhancement Challenge
A Progressive Enhancement Challenge êŽë š
Letâs say youâve got some interactive element.
This element works perfectly fine in just HTML, which is the foundation of progressive enhancement.
And now, in your JavaScript, the functionality this button provides isnât really necessary anymore, and your plan is to hide this element.
What is the best way to accomplish this?
Note
I think itâs good to think of this abstractly, but if what Iâve presented above is so abstract that it makes it hard to think about, here are some examples:
- A âLoad Moreâ anchor link that loads the next set of items (i.e.
<a href="?page=3">Load More</a>) which you donât need after JavaScript loads because youâve implemented an infinite scroll UX. - A âSaveâ button that saves user-entered information on the page to the database (i.e.
<button onclick="save()">Save</button>) which you donât need after JavaScript loads because youâve implemented auto-saving functionality.
A âjsâ Class
A classic approach to this is hiding the button when you know JavaScript is available. You put something like this pretty early in your HTML:
<script> document.documentElement.classList.add("js"); </script>
If this executes, youâve proven that JavaScript is available, so you hide the button:
html.js {
.save-button {
display: none;
}
}
As appealing as this looks, it may not be the catch-all perfect solution.
Downsides
- Youâve proven here that JavaScript is available, but you arenât checking if the particular JavaScript that does the auto-saving is loaded and has run successfully. You can probably account for that by applying a more specific class just for this situation and applying it after the code that implements auto-saving.
- The longer you (necessarily) have to wait for the JavaScript to be done, the longer the button is visible on the screen. This is likely to cause a âflashâ of the button being there where is doesnât need really need to be.
On States
This question came up for me from a ShopTalk Show listener Tibor Leupold writing in asking about it. He was concerned about layout shift as a result of hiding the element(s) as well as the awkward UX.
Note
Letâs get this one out of the way: couldnât you just⊠leave the interactive elements there but change their functionality when the JavaScript loads? Maybe? Probably? Thatâs skirting the question though. Letâs assume the web is a big place with an unknowable amount of situations and that this particular situation of needing/wanting to hide an element with minimal impact is a reasonable one.
A way to think about our needs here is that there are three states to concern ourselves with:
- JavaScript is unavailable entirely
- Before the relevant JavaScript has loaded and executed
- The relevant JavaScript is loaded and executed successfully
No JS
Weâre probably not going to hide the button by default, as we donât have a mechanism for un-hiding it in a no-JS situation. So we basically donât need to do anything to accomplish this state, just have the interactive element on the page and functional in HTML.
In the reverse situation, where you have an element on the page that only works with JavaScript, you can hide it in a no-JS situation like:
<noscript>
<style> .js-only-interactive-element {
display: none;
} </style>
</noscript>
Before JS Loaded
This is the hardest state. In this state, perhaps we know that JavaScript is available, but we donât know how long itâs going to take or even if the JavaScript we care about is going to execute successfully.
It seems like the ideal behavior would be âhide the interactive element for a brief period, then if the relevant JavaScript isnât ready, show the element.â But how?! We canât count on JavaScript for this behavior, which is the only technology Iâm aware of that could do it. Rock and a hard place!
Maybe there is some extremely exotic technique involving HTML streaming that could delay the âsendâ of the interactive element down from the network for that brief blocking period? Thatâd be wild.
Another thing I think of is the behavior of font-display: block;. This is about the behavior of loading custom fonts via CSS @font-face. It can tell the browser how to behave while the custom font it loading. Do you want the browser to wait to see if the custom font loads and then âswapâ to it? Youâve got options. The block value says:
Gives the font face a short block period and an infinite swap period.
Seems related! Maybe there is a way to bring this kind of behavior to progressive enhancement elements to mimic the behavior we want: âhide the interactive element for a brief period, then if the relevant JavaScript isnât ready, show the element.â Help us, web platform.
JS Ready
This is a fairly straightforward state, but itâs the cause of the âflashâ and potential layout shift.
function setUpInfiniteScroll() {
// do all the work
// at the end, say it's ready
document.documentElement.classList.add("infinite-scroll-ready");
}
.infinite-scroll-ready {
.load-more-link {
display: none;
}
}
The problem here is: how long is that âLoad Moreâ link going to be on the page before it disappears? Is it fairly instant? A few hundred milliseconds? Eight seconds? Never? (You really canât know.)
Also: will the layout shift it triggers cause the user to potentially click on something they didnât mean to? Maybe hiding can be done without the layout shift?
.infinite-scroll-ready {
.load-more-link {
visibility: hidden;
}
}
Is there a better way?
I feel like people have been thinking about progressive enhancement for a couple decades now. Is there an extremely clean/simple way to do this that Iâm just not seeing?