A Progressive Enhancement Challenge

Chris Coyier Chris Coyier on

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?

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:

  1. 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.
  2. 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>Code language: HTML, XML (xml)

If this executes, you’ve proven that JavaScript is available, so you hide the button:

html.js {
  .save-button {
    display: none;
  }
}Code language: CSS (css)

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.

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:

  1. JavaScript is unavailable entirely
  2. Before the relevant JavaScript has loaded and executed
  3. 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>Code language: HTML, XML (xml)

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 thnis 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");
}Code language: JavaScript (javascript)
.infinite-scroll-ready {
  .load-more-link {
    display: none;
  }
}Code language: CSS (css)

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;
  }
}
Code language: CSS (css)

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?

It's time to take your JavaScript to the next level

Frontend Masters logo

Frontend Masters is the best place on the web to really learn JavaScript. We have a complete learning path from the biggest and best teachers in JavaScript to help you make the most out of the web's biggest language.

7-Day Free Trial

One response to “A Progressive Enhancement Challenge”

Leave a Reply

Your email address will not be published. Required fields are marked *

$839,000

Frontend Masters donates to open source projects through thanks.dev and Open Collective, as well as donates to non-profits like The Last Mile, Annie Canons, and Vets Who Code.