Home
| Colors: |

HTML Element Fallback Visibility: The Button Problem

September 2025

Defining Use Cases

A question came up on the ShopTalk Show podcast:

How can you have an HTML button that acts as a fallback if some JavaScript on the page doesn't load.

The idea being that the JavaScript's functionality would replace the need for the button.

More specifically, the goals break down like this:

  • If JavaScript IS available and it DOES load, then:

    Never show the HTML fallback button. Only show the JavaScript enhanced content.

  • If JavaScript IS available but DOES NOT load, then:

    Show the HTML fallback button

  • If JavaScript IS NOT available, then:

    Show the HTML fallback button

The way I'm handling that is through a combination of <noscript> and inline <script> tags. It looks like this:

My solution:

<!DOCTYPE html>
<html lang="en">
  <meta charset="UTF-8" />
  <head>
    <style>
    :root {
      --load-hider: 0;
    }
    .load-hider {
      opacity: var(--load-hider);
    }
    </style>
  </head>
  <body>

    <noscript>
      <p>
        This page uses JavaScript which appears
        to be turned off, etc...
      </p>
      <style>
        :root { --load-hider: 1; }
      </style>
    </noscript>

    <div class="dymanic-container">
      <button class="load-hider">
        Click Me
      </button>
    </div>

    <!-- This script removes the button
    from the DOM and changes `load-hider`
    to `visible` -->
    <script 
      src="DYNAMIC_CONTAINER_SCRIPT.js"
      type="module"
    ></script>

    <script>
      setTimeout(() => { 
        document.documentElement.style.setProperty(
          "--load-hider", "1");
      }, 1500);
    </script>

  </body>
</html>

The Details

  • The DYNAMIC_CONTAINER_SCRIPT.js file represents a script that updates the contents of the <div class=dymanic-container"> and removes the <button> so it's visibility no longer matters.
  • The setTimeout() function in the inline <script> fires after 1500 milliseconds. When it does it updates the CSS variable from hidden to visible. If the button (or anything else with the class) is still on the page, it becomes visible.

    If all the element have been removed by the external script there's no visible change to the page.

  • If JavaScript has been turned off the <noscript> tag fires immediately, updating the CSS variable and making the button visible.

The only time the button would show and then be replaced by the JavaScript functionality is if DYNAMIC_CONTAINER_SCRIPT.js takes longer than 1500ms to load. So, this doesn't provide 100% coverage, but by tweaking the delay you can cover the vast majority of interactions.

Full Page Invisibility

The same technique can be used to on the full page. I warp all my pages in a bitty tag for basic functionality. By applying the .load-hider class to it it's rare that anything flashes, jumps, or moves around visually when the page loadstiming.

-a

end of line

Endnotes

I expect there are cases where things get so screwed up that JavaScript is on (which prevents the noscript tag from firing), but something is so screwed up the inline code doesn't work.

If you're in that situation, you've got bigger problems that are outside the scope of just trying to keep things from blinking on the page.

Switching to using opacity: 0 / opacity: 1 instead of visibility: hidden / visibility: visible allows for using transition: opacity 2000ms; type changes. (Since visibility is binary there's no keyframe calculation to work off of to do a smooth transition)

Another possible feature to add is if the on page timer threshold is passed and it fires then drop a flag on the page so if the external javascript does finish loading it doesn't actually do anything. This, of course, assumes it's only progressive enhancement and you've got everything a user would need on the page without it.

I think that would let you eliminate page shift in just about every scenario.

I've been messing around with doing this for my entire page. If my theory's right, you could prevent page shift a huge percentage of the time.

Here's where the question came from:

Footnotes

It may take a tick longer for content to show up, but I prefer that to having it jump around. The idea that folks can see content faster and then have it update doesn't fit with my experience of the web in almost all cases. I'd rather wait a moment or two longer than start reading something and have it shift out from under me.