{"id":7324,"date":"2025-10-03T11:06:19","date_gmt":"2025-10-03T16:06:19","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=7324"},"modified":"2025-10-22T15:27:27","modified_gmt":"2025-10-22T20:27:27","slug":"a-progressive-enhancement-challenge","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/a-progressive-enhancement-challenge\/","title":{"rendered":"A Progressive Enhancement Challenge"},"content":{"rendered":"\n<p>Let&#8217;s say you&#8217;ve got some interactive element.<\/p>\n\n\n\n<p>This element works perfectly fine in just HTML, which is the foundation of progressive enhancement.<\/p>\n\n\n\n<p>And now, in your JavaScript, the functionality this button provides isn&#8217;t really necessary anymore, and your plan is to <strong>hide<\/strong> this element.<\/p>\n\n\n\n<p>What is the best way to accomplish this?<\/p>\n\n\n\n<div class=\"wp-block-group learn-more\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained\">\n<p>I think it&#8217;s good to think of this abstractly, but if what I&#8217;ve presented above is so abstract that it makes it hard to think about, here are some examples:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A &#8220;Load More&#8221; anchor link that loads the next set of items (i.e. <code>&lt;a href=\"?page=3\"&gt;Load More&lt;\/a&gt;<\/code>) which you don&#8217;t need after JavaScript loads because you&#8217;ve implemented an infinite scroll UX.<\/li>\n\n\n\n<li>A &#8220;Save&#8221; button that saves user-entered information on the page to the database (i.e. <code>&lt;button onclick=\"save()\"&gt;Save&lt;\/button&gt;<\/code>) which you don&#8217;t need after JavaScript loads because you&#8217;ve implemented auto-saving functionality.<\/li>\n<\/ol>\n<\/div><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">A &#8220;js&#8221; Class<\/h2>\n\n\n\n<p>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:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>&gt;<\/span><span class=\"javascript\">\n  <span class=\"hljs-built_in\">document<\/span>.documentElement.classList.add(<span class=\"hljs-string\">\"js\"<\/span>);\n<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If this executes, you&#8217;ve proven that JavaScript is available, so you hide the button:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">html<\/span><span class=\"hljs-selector-class\">.js<\/span> {\n  .save-button {\n    <span class=\"hljs-attribute\">display<\/span>: none;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>As appealing as this looks, it may not be the catch-all perfect solution.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Downsides:<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>You&#8217;ve proven here that JavaScript is available, but you aren&#8217;t checking if the <em>particular<\/em> 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.<\/li>\n\n\n\n<li>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 &#8220;flash&#8221; of the button being there where is doesn&#8217;t need really need to be.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">On States<\/h2>\n\n\n\n<p>This question came up for me from a ShopTalk Show listener Tibor Leupold writing in asking about it. He was concerned about <strong>layout shift<\/strong> as a result of hiding the element(s) as well as the awkward UX. <\/p>\n\n\n\n<p class=\"learn-more\">Let&#8217;s get this one out of the way: <strong>couldn&#8217;t you just&#8230; leave the interactive elements there but change their functionality when the JavaScript loads?<\/strong> Maybe? Probably? That&#8217;s skirting the question though. Let&#8217;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.<\/p>\n\n\n\n<p>A way to think about our needs here is that there are three states to concern ourselves with:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>JavaScript is unavailable entirely<\/li>\n\n\n\n<li>Before the relevant JavaScript has loaded and executed<\/li>\n\n\n\n<li>The relevant JavaScript is loaded and executed successfully<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">No JS<\/h3>\n\n\n\n<p>We&#8217;re probably not going to hide the button by default, as we don&#8217;t have a mechanism for un-hiding it in a no-JS situation. So we basically don&#8217;t need to do anything to accomplish this state, just have the interactive element on the page and functional in HTML. <\/p>\n\n\n\n<p>In the <strong>reverse situation<\/strong>, where you have an element on the page that <em>only<\/em> works with JavaScript, you can hide it in a no-JS situation like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">noscript<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">style<\/span>&gt;<\/span><span class=\"css\">\n    <span class=\"hljs-selector-class\">.js-only-interactive-element<\/span> {\n      <span class=\"hljs-attribute\">display<\/span>: none;\n    }\n  <\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">style<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">noscript<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Before JS Loaded<\/h3>\n\n\n\n<p>This is the hardest state. In this state, perhaps we know that JavaScript is available, but we don&#8217;t know <em>how long<\/em> it&#8217;s going to take or even <em>if<\/em> the JavaScript we care about is going to execute successfully.<\/p>\n\n\n\n<p>It seems like the ideal behavior would be &#8220;hide the interactive element for a brief period, then if the relevant JavaScript isn&#8217;t ready, show the element.&#8221; But how?! We can&#8217;t count on JavaScript for this behavior, which is the only technology I&#8217;m aware of that could do it. Rock and a hard place!<\/p>\n\n\n\n<p>Maybe there is some extremely exotic technique involving HTML streaming that could delay the &#8220;send&#8221; of the interactive element down from the network for that brief blocking period? That&#8217;d be wild.<\/p>\n\n\n\n<p>Another thing I think of is the behavior of <code><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/@font-face\/font-display#block\">font-display: block;<\/a><\/code>. This is about the behavior of loading custom fonts via CSS <code>@font-face<\/code>. It can tell the browser how to behave while the custom font it loading. Do you want the browser to <em>wait<\/em> to see if the custom font loads and then &#8220;swap&#8221; to it? You&#8217;ve got options. The <code>block<\/code> value says:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Gives the font face a short block period and an infinite swap period.<\/p>\n<\/blockquote>\n\n\n\n<p>Seems related! Maybe there is a way to bring this kind of behavior to progressive enhancement elements to mimic the behavior we want: &#8220;hide the interactive element for a brief period, then if the relevant JavaScript isn&#8217;t ready, show the element.&#8221; Help us, web platform.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">JS Ready<\/h3>\n\n\n\n<p>This is a fairly straightforward state, but it&#8217;s the cause of the &#8220;flash&#8221; and potential layout shift.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">setUpInfiniteScroll<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-comment\">\/\/ do all the work<\/span>\n\n  <span class=\"hljs-comment\">\/\/ at the end, say it's ready<\/span>\n  <span class=\"hljs-built_in\">document<\/span>.documentElement.classList.add(<span class=\"hljs-string\">\"infinite-scroll-ready\"<\/span>);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.infinite-scroll-ready<\/span> {\n  .load-more-link {\n    <span class=\"hljs-attribute\">display<\/span>: none;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The problem here is: how long is that &#8220;Load More&#8221; link going to be on the page before it disappears? Is it fairly instant? A few hundred milliseconds? Eight seconds? Never? (You really can&#8217;t know.) <\/p>\n\n\n\n<p>Also: will the layout shift it triggers cause the user to potentially click on something they didn&#8217;t mean to? Maybe hiding can be done without the layout shift?<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-selector-class\">.infinite-scroll-ready<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>  .load-more-link {\n<\/span><\/span><mark class='shcb-loc'><span>    <span class=\"hljs-attribute\">visibility<\/span>: hidden;\n<\/span><\/mark><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Is there a better way?<\/h2>\n\n\n\n<p>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&#8217;m just not seeing? <\/p>\n","protected":false},"excerpt":{"rendered":"<p>You want to hide an interactive element that you don&#8217;t need anymore after JavaScript loads\/runs. Can you do it without a &#8220;flash&#8221; or layout shift?<\/p>\n","protected":false},"author":1,"featured_media":7328,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[93,7,31,3,139],"class_list":["post-7324","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-buttons","tag-css","tag-html","tag-javascript","tag-progressive-enhancement"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/10\/ghost-button.jpg?fit=2100%2C1500&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/7324","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=7324"}],"version-history":[{"count":11,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/7324\/revisions"}],"predecessor-version":[{"id":7493,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/7324\/revisions\/7493"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/7328"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=7324"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=7324"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=7324"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}