Building · 2026-08-10 · 8 min read · By Arden Talbot, founder of SheetLink
Progressive enhancement is a form feature
The HTML form is the original progressively enhanced interface. An endpoint that works without JavaScript is not a nostalgia act - it is the difference between a form that mostly works and one that always does.
Older than JavaScript, and still standing
The HTML form predates JavaScript. It shipped with the earliest browsers and was standardized in HTML 2.0 in 1995: a declarative block of inputs, a submit button, an action URL, and a method. The browser did everything - collected the values, encoded them, sent the request, rendered the response. No script ran. None existed to run.
Thirty years later that machinery still works in every browser ever shipped, which makes the form something rare in web development: an interface with a guaranteed floor. Progressive enhancement - the discipline of building a working baseline and layering improvements on top - was articulated as a philosophy years later, but the form had been quietly practicing it all along. This essay is about why that floor is worth keeping under your forms in 2026, and how to build upward from it without knocking it out.
What progressive enhancement actually claims
The idea is often flattened into "support users with JavaScript disabled," which makes it easy to dismiss - almost nobody disables JavaScript on purpose. The real claim is subtler: build the essential interaction out of resilient materials, then add the pleasant parts in fragile ones. HTML and HTTP are the resilient materials. Script is the fragile one - not because it is badly engineered, but because it has the most ways to be absent or broken at the moment of truth.
For most page furniture, a script failure degrades the experience. For a form, it can destroy the interaction entirely: a submit button wired only to a dead event handler is a button that does nothing, attached to a message that goes nowhere.
The baseline: action, method, redirect
Here is a complete, working form with no script anywhere:
<form action="https://sheetlinkforms.com/f/YOUR_TOKEN" method="POST">
<label for="em">Email</label>
<input id="em" type="email" name="email" required>
<input name="_slhp" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px">
<button>Send</button></form>
The browser posts the fields as a urlencoded body; the endpoint answers with a 303 redirect to a thank-you page, or to a custom URL you configure - the classic POST-redirect-GET pattern, covered in our redirect guide. The hidden _slhp input is the honeypot; bots fill it, humans never see it. Every layer we add later sits on top of this, and this keeps working if every one of those layers fails.
Who actually lives at the baseline
"Users without JavaScript" sounds like a rounding error until you enumerate who is temporarily in that bucket. Visitors whose content blocker took your script out as collateral - blockers work from filter lists, and third-party scripts are exactly what those lists target. Visitors on a train, whose HTML arrived but whose script request died in a tunnel. Visitors on old devices or locked-down corporate browsers where a modern syntax error halts your bundle. Visitors caught in the seconds before a slowly loading script attaches its handlers, who click submit into the void.
Each group is small. They do not overlap much, so they sum. And a form is disproportionately likely to matter to them - it is how someone reports a problem, requests a quote, or asks for help, often precisely when their connectivity is at its worst.
JavaScript fails more often than it is disabled
The honest framing is not "what if JS is off" but "what if this particular script did not run on this particular page load." A CDN blip, a race between your handler and an eager click, an extension that patches fetch, a partial page load in a flaky webview - none of these are exotic. On a form that is wired exclusively through script, every one of them converts a would-be submission into silence, and silence is the worst failure mode a form has. Nobody reports it. The visitor assumes you got the message; you assume there was no message.
A form with a real action attribute turns all of those failures into a slightly less smooth success: the browser falls back to a full-page POST and the data still lands.
The enhancement layer, done properly
None of this argues against AJAX - inline success without a page reload is a genuinely better experience. It argues for the order of construction: enhancement on top of the baseline rather than instead of it.
That is how our embed works. One script tag - <script src="https://sheetlinkforms.com/sl.js" data-token="..."> - finds forms marked data-sheetlink and any form whose action already points at the endpoint, intercepts submission, posts via fetch, and swaps in inline success text. It also injects the honeypot automatically and adds a timing signal that helps separate humans from bots. If the script never loads, nothing is lost: the action attribute is still there, and the form submits the 1995 way. The pattern - and how to build the same layering yourself without our script - is in the AJAX form submission tutorial and the docs.
Even single-page apps have a floor
SPAs would seem to be where this argument dies - the whole page is script, so there is no baseline to preserve. But the discipline still pays inside the component. Render a real <form> element with a real action, not a div with a click handler; let the framework intercept submission when it is alive. The embed script handles the SPA case with a MutationObserver, binding forms as the framework renders them - relevant for React, Vue and Nuxt, and SvelteKit alike.
You keep the browser's free machinery too: Enter-to-submit, native required and type validation, autofill. A div with a click handler has to reimplement all of it, and usually reimplements most of it.
Accessibility comes with the baseline, free
The native form stack is the most accessibility-tested code you will ever ship, because you did not ship it - browser and screen-reader vendors did. Labels associate with inputs, focus moves predictably, validation states are announced, submission works from the keyboard. Build on those semantics and assistive technology mostly just works.
Replace them with custom script-driven widgets and every one of those behaviors becomes your job again, which in practice means some of them will be done and some will be discovered missing by the first screen-reader user who needs the form. Progressive enhancement is sometimes framed as serving edge cases; here it is simply the cheapest possible route to an accessible form.
Attribution as a well-behaved enhancement
One more layer shows the pattern's shape. Marketing attribution - which campaign produced this lead - depends on script: sl.js captures utm_source, utm_medium, utm_campaign and click IDs like gclid and fbclid from the landing URL, persists them in localStorage across the visitor's journey, and attaches them when a form finally submits. The mechanics are in the gclid capture guide.
Notice the degradation contract. Script present: the lead lands with its full ad-click history in dedicated columns. Script absent: the lead still lands, minus attribution. The enhancement enriches the core interaction and is never load-bearing for it. That is the whole philosophy in one feature.
The quiet virtue
Progressive enhancement is unfashionable because its benefits are invisible - a resilient form looks identical to a fragile one in every demo. The difference only shows up in the aggregate: the submissions that arrive from the train, from behind the content blocker, from the browser you never tested. Those are real people with real intent, and whether you hear from them is decided by an architecture choice that costs almost nothing to make correctly at the start.
Keep the action attribute real. Let the script be a gift, not a dependency. If you want to see the layered version working, the live demo submits into a public sheet - with the enhancement on top and the baseline underneath. SheetLink Forms is free to start - start free.
FAQ
Does anyone really browse with JavaScript disabled anymore?
Deliberately, very few. But the practical population is larger: content blockers stripping third-party scripts, network failures that drop the bundle, extensions and corporate proxies interfering, and the window before handlers attach. The baseline serves everyone whose script did not run this time, whatever the reason.
How does a form submission work with no JavaScript at all?
The browser encodes the fields and POSTs them to the URL in the form's action attribute. SheetLink Forms answers HTML posts with a 303 redirect to a thank-you page or a custom URL you set - the POST-redirect-GET pattern described in the redirect guide.
What does the sl.js embed add on top of the plain form?
AJAX submission with inline success text instead of a page navigation, automatic honeypot injection, a timing signal for spam scoring, capture of UTM parameters and ad click IDs persisted across pages, and SPA support via MutationObserver. Remove it and the form still submits normally. See the docs.
Can an ad blocker break my form?
It can block a script, which is why the form should not depend on one. With a real action attribute, a blocked sl.js means the visitor gets a full-page submit and redirect instead of inline success - a smoothness downgrade, not a failure. A form wired only through script has no such fallback.
Is progressive enhancement still relevant inside a React or Vue app?
Why does the honeypot need to work without JavaScript too?
Because spam bots often skip script entirely and POST straight to the endpoint. A honeypot that only exists when sl.js injects it would miss them, so the recommended markup includes the _slhp field directly in the HTML. The honeypot glossary entry has the details.
Does the no-JavaScript path lose ad attribution?
Attribution capture is script-based, so a submission made without sl.js lands without the persisted UTM and click ID columns - though any values present in hidden fields you add yourself still come through. The lead itself is never lost; attribution degrades, delivery does not.
Is a full-page redirect worse for conversion than inline success?
Inline success is smoother, which is why the enhancement exists. But a dedicated thank-you page has its own virtues - it is bookmarkable, trackable as a conversion event, and unambiguous. The thank-you page entry covers when each shape fits.
A form with a floor under it
Plain HTML in, rows out - and the AJAX layer is one script tag when you want it.
Start freeSee the live demoThe case against writing your own form APIThe form UX details that raise conversion
