Imagining a JavaScript-less SPA World
A brain dump exploring what browsers might need in order to deliver the snappy feel of today’s single page apps without any JavaScript. The idea came from asking ChatGPT to outline a true “JS‑free” SPA.
The Core Idea
- New form attributes
async
to fetch responses without a full reloadupdate="#target"
to inject the returned fragment into a selector
- CSS pseudo-classes
:loading
to style the in-flight state for spinners and disabled buttons
With only HTML and CSS we could get async submission, live loading indicators and partial DOM updates.
Working with Current APIs
Progressive enhancement would still rely on a plain <form>
fallback. Today you can hack around it with hidden iframes and CSS-only tricks, but real apps still need bits of JS.
What Browsers Would Need
- HTML: define the
async
boolean on forms and anupdate
attribute for selectors. - CSS: introduce
:loading
,:error
,:idle
and other states tied to network activity. - Fetch & Insert: the browser would fetch the form and swap the returned fragment into the update target automatically.
- Error & Progress: declarative hooks for status messages and retries.
Example Markup
Below is a minimal page showing how a browser could handle an asynchronous form without any scripting:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Async Form without JS</title>
<style>
/* hide the loader by default */
form[async] .loader { display: none; }
/* when the form is in-flight, show the loader */
form[async]:loading .loader { display: inline-block; }
</style>
</head>
<body>
<!--
async → tell the browser "do this submission via XHR/fetch under the hood"
update → CSS selector for where to inject the response HTML
-->
<form action="/subscribe" method="post" async update="#message">
<label>
Email
<input type="email" name="email" required>
</label>
<button type="submit">Subscribe</button>
<!--
this element is automatically shown while the request is pending,
thanks to the :loading pseudo-class above
-->
<span class="loader">Sending…</span>
</form>
<!-- server can return just a fragment of HTML to replace this -->
<div id="message"></div>
</body>
</html>
The important rule is:
form:loading .loader {
display: inline-block;
}
Accessibility
These features would need to map :loading
to aria-busy="true"
, manage focus on injected content and announce loading messages to screen readers.
Interim Solutions
Libraries like htmx or Hotwire Turbo offer much of this with small JavaScript payloads, but native support would further reduce complexity and script overhead.
Wider Implications
Faster initial loads, fewer XSS vectors and simpler build pipelines. But questions remain around file uploads, push updates and standardising declarative error handling.
Why We Write So Much JavaScript
Modern frameworks evolved as workarounds for gaps in HTML and CSS. Much of the code in today’s apps simply compensates for missing browser features. When the template element shipped, frameworks didn’t vanish—they became leaner and new ones emerged. Similar declarative network controls could again lighten our toolkits. Some resistance may come from developers who are understandably attached to the frameworks they know best.
Open Questions
The markup above doesn’t address routing or persistent state. A future spec might allow reading and writing to local or session storage without scripts, perhaps via form-backed inputs or special attributes. There is still plenty to explore in how a JS-less UI would manage client-side data.
Feedback is welcome—what other features would you want to see in a JS‑less world?