Progressive Enhancement & Zero-JS Websites in 2026
I shipped a marketing site last month that scores 100 across all Lighthouse categories. Total JavaScript sent to the client: zero bytes. Not "almost zero" or "minimal" -- literally none. The forms work, the navigation works, there's a dark mode toggle, and the page transitions feel snappy. Five years ago that would've required serious compromises. In 2026, the platform has caught up to a point where zero-JS isn't a limitation -- it's a legitimate architectural choice.
But here's the thing most articles about progressive enhancement get wrong: it's not about being a purist or hating JavaScript. It's about making intentional decisions about what runs where. Let me walk you through how we approach this at Social Animal, what's changed in 2026 that makes zero-JS viable for more projects than ever, and when you absolutely should still reach for client-side code.
Table of Contents
- What Progressive Enhancement Actually Means in 2026
- The Platform Has Changed Everything
- CSS-Only Patterns That Replace JavaScript
- HTML-First Interactivity
- Server-Side Frameworks That Ship Zero JS
- When Zero JavaScript Is the Wrong Choice
- Performance Benchmarks: JS vs Zero-JS
- Building a Progressive Enhancement Strategy
- Real-World Architecture for Zero-JS Sites
- FAQ

What Progressive Enhancement Actually Means in 2026
Progressive enhancement has been around since the early 2000s, but most developers I talk to still misunderstand it. They think it means "build a crappy HTML version first, then make it good with JavaScript." That's backwards.
Progressive enhancement means your baseline experience works. Period. HTML is your foundation. CSS adds the visual layer. JavaScript -- if you need it -- adds interactivity on top. Each layer is additive. If any layer fails, the layers below still function.
In 2026, this philosophy has become more practical than ever because the baseline capabilities of HTML and CSS have expanded dramatically. Things that required JavaScript five years ago now have native platform solutions:
- Accordions and disclosure widgets →
<details>and<summary> - Modals and dialogs →
<dialog>element - Form validation → Constraint Validation API
- Smooth scrolling →
scroll-behavior: smooth - Dark mode →
@media (prefers-color-scheme)with:has()selector tricks - Carousels → CSS scroll snap with
scrollbar-width - Popovers and tooltips → Popover API
- Anchor positioning → CSS Anchor Positioning
- View transitions → View Transitions API (Level 2 for cross-document)
The web platform in 2026 isn't the web platform of 2020. We've had a massive expansion of what's possible without scripting.
The Platform Has Changed Everything
The Popover API
The Popover API hit full cross-browser support in 2024 and by now it's production-ready everywhere that matters. Before this, every tooltip, dropdown menu, and toast notification needed JavaScript. Now:
<button popovertarget="my-menu">Menu</button>
<nav popover id="my-menu">
<a href="/about">About</a>
<a href="/work">Work</a>
<a href="/contact">Contact</a>
</nav>
That's it. Click the button, the popover appears. Click outside, it dismisses. Press Escape, it dismisses. Focus management is handled. Accessible by default. Zero JavaScript.
CSS Anchor Positioning
This is the one that really opened things up. Positioning a tooltip relative to its trigger used to require JavaScript to measure DOM positions. CSS Anchor Positioning (baseline in 2025, fully stable now) handles it declaratively:
.trigger {
anchor-name: --my-trigger;
}
.tooltip {
position: fixed;
position-anchor: --my-trigger;
top: anchor(bottom);
left: anchor(center);
translate: -50% 8px;
}
Combine this with the Popover API and you've got fully positioned, accessible tooltips with zero client-side code.
Cross-Document View Transitions
View Transitions Level 2 is the one that makes zero-JS sites feel like SPAs. You add a CSS at-rule and suddenly navigating between pages has smooth animated transitions:
@view-transition {
navigation: auto;
}
::view-transition-old(root) {
animation: fade-out 0.2s ease;
}
::view-transition-new(root) {
animation: fade-in 0.2s ease;
}
Chrome, Edge, and Safari all support this now. Firefox is expected later this year. This single feature eliminates one of the biggest reasons teams chose SPAs -- perceived performance through animated transitions.
The `:has()` Selector
The :has() selector (sometimes called the "parent selector") has been stable since 2024 and it's genuinely transformative for CSS-only interactivity:
/* Toggle dark mode without JS */
html:has(#dark-mode:checked) {
color-scheme: dark;
--bg: #1a1a2e;
--text: #eee;
}
With a hidden checkbox and a <label>, you've got a working dark mode toggle. No JavaScript. The state persists during the session and you can even sync it to localStorage via a tiny enhancement script if you want persistence across visits.
CSS-Only Patterns That Replace JavaScript
Let me catalog the patterns we use regularly. I'm not talking about CSS art or novelty demos -- these are production patterns we ship to real clients.
| Pattern | Old Approach (JS) | 2026 Approach (CSS/HTML) | Browser Support |
|---|---|---|---|
| Dropdown menus | Event listeners, focus traps | Popover API + :has() |
95%+ |
| Accordions | Toggle classes, ARIA management | <details> + ::details-content |
96%+ |
| Modals | Focus trap libraries, scroll lock | <dialog> + ::backdrop |
97%+ |
| Tabs | Show/hide panels, ARIA tabs | Radio buttons + :has() + scroll-snap |
95%+ |
| Carousels | Swiper.js, Flickity | scroll-snap + scroll-timeline |
93%+ |
| Tooltips | Popper.js, Floating UI | Popover API + Anchor Positioning | 90%+ |
| Form validation | Custom validation logic | Constraint Validation + :user-valid |
95%+ |
| Scroll animations | Intersection Observer, GSAP | animation-timeline: scroll() |
88%+ |
| Theme toggle | localStorage + DOM manipulation | Checkbox + :has() + color-scheme |
96%+ |
| Page transitions | Client-side routing | Cross-document View Transitions | 85%+ |
That table represents probably 80% of the interactive patterns on a typical marketing site or content platform. All achievable without shipping a single kilobyte of JavaScript.
The Tabs Pattern
Here's one I'm particularly fond of. CSS-only tabs using radio buttons:
<div class="tabs">
<input type="radio" name="tab" id="tab1" checked>
<label for="tab1">Features</label>
<input type="radio" name="tab" id="tab2">
<label for="tab2">Pricing</label>
<input type="radio" name="tab" id="tab3">
<label for="tab3">FAQ</label>
<div class="panels">
<div class="panel" id="panel1">Features content...</div>
<div class="panel" id="panel2">Pricing content...</div>
<div class="panel" id="panel3">FAQ content...</div>
</div>
</div>
.tabs:has(#tab1:checked) .panels { --active: 0; }
.tabs:has(#tab2:checked) .panels { --active: 1; }
.tabs:has(#tab3:checked) .panels { --active: 2; }
.panels {
display: flex;
overflow: hidden;
translate: calc(var(--active) * -100%) 0;
transition: translate 0.3s ease;
}
.panel {
min-width: 100%;
}
Smooth, animated tab switching with zero JavaScript. Add role="tablist" and appropriate ARIA attributes for accessibility, and you've got a production-ready component.

HTML-First Interactivity
Beyond CSS, HTML itself has gotten a lot more capable. Let me highlight patterns we use.
The `
I know <dialog> has been around for a while, but many teams still reach for a modal library. Don't. The native dialog handles focus trapping, scroll locking, Escape to close, and the ::backdrop pseudo-element for overlays.
The one catch: you do need a tiny bit of JavaScript to open a modal dialog (calling .showModal()). But for progressive enhancement, you can make the trigger a link to a separate page, then enhance with JS if available:
<a href="/contact" class="js-dialog-trigger" data-dialog="contact-form">
Get in touch
</a>
<dialog id="contact-form">
<form method="dialog">
<!-- form fields -->
<button type="submit">Send</button>
</form>
</dialog>
Without JavaScript: the user navigates to /contact. With JavaScript: the dialog opens inline. Both work. That's progressive enhancement.
Forms Without JavaScript
Forms are the biggest win for zero-JS approaches. Native HTML forms submit data to servers. That's what they were designed to do. With modern server-side frameworks, you don't need e.preventDefault() and fetch() calls:
<form action="/api/contact" method="POST">
<input type="email" name="email" required>
<textarea name="message" required minlength="10"></textarea>
<button type="submit">Send</button>
</form>
The :user-valid and :user-invalid pseudo-classes (now baseline) let you style validation states without JS, but only after the user has interacted -- no more red borders on page load.
input:user-invalid {
border-color: var(--error);
outline-color: var(--error);
}
input:user-valid {
border-color: var(--success);
}
Server-Side Frameworks That Ship Zero JS
Choosing the right framework matters enormously for progressive enhancement. Here's how the major players stack up in 2026.
Astro
Astro remains the gold standard for zero-JS output. It ships HTML and CSS by default, and you opt into JavaScript per-component with client: directives. We use it extensively for marketing sites, documentation, and content-heavy platforms -- see our Astro development capabilities for specifics.
---
// This component ships ZERO JavaScript
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
---
<ul>
{posts.map(post => <li><a href={post.url}>{post.title}</a></li>)}
</ul>
Astro 5 (stable since early 2025) added server islands and improved content layer APIs. The mental model is simple: everything is server-rendered unless you explicitly say otherwise.
Eleventy (11ty)
Eleventy 3.0 continues to be excellent for zero-JS sites. It's a pure static site generator -- no opinions about client-side JavaScript at all. If you want it, you add it manually. We've found it ideal for smaller sites and blogs where the build-time simplicity matters.
Next.js with Server Components
Next.js is interesting here. Server Components (the default in App Router) don't ship JavaScript to the client. But the Next.js runtime itself adds a baseline JS payload for hydration, routing, and prefetching. You can't get to true zero-JS with Next.js, but you can get remarkably close for interactive applications. Check out our Next.js development work -- we've pushed this boundary on several projects.
SvelteKit
SvelteKit lets you disable JavaScript per-page with export const csr = false. The output is pure HTML/CSS. It's a great middle ground -- you get the developer experience of Svelte components but can selectively disable client-side rendering.
| Framework | Default JS Output | Zero-JS Possible? | Best For |
|---|---|---|---|
| Astro 5 | 0 KB | Yes (default) | Content sites, marketing |
| Eleventy 3 | 0 KB | Yes (default) | Blogs, docs, simple sites |
| Next.js 15 | ~85-100 KB | No (runtime required) | Web apps, dynamic content |
| SvelteKit 2 | ~15-25 KB | Yes (per-page opt-out) | Hybrid sites |
| Fresh (Deno) | 0 KB | Yes (island architecture) | Deno-based projects |
| Enhance | 0 KB | Yes (HTML-first) | Web component sites |
When Zero JavaScript Is the Wrong Choice
I'd be doing you a disservice if I only talked about when zero-JS works. Here's when it doesn't:
Real-time collaboration. If you're building something like Figma, Google Docs, or a chat application, you need WebSockets and client-side state management. No way around it.
Complex data visualization. D3, Observable Plot, or deck.gl for maps -- these need JavaScript. You could server-render static charts as SVG (and we do), but anything interactive needs client code.
Rich text editors. ProseMirror, TipTap, Lexical -- these are inherently client-side applications. Progressive enhancement here means providing a <textarea> fallback, which is actually pretty reasonable.
Client-side search. If you want instant search-as-you-type without hitting the server on every keystroke, you need client-side search indexes (Pagefind, Lunr, Fuse.js). Pagefind is worth calling out specifically -- it's a build-time search index that loads only ~5 KB initially.
Authentication flows. OAuth redirects work without JS, but token refresh, session management, and protected client-side routes typically need some scripting.
Video/audio players. Custom players need JavaScript. But <video> and <audio> elements with native controls work perfectly without it.
The pattern I recommend: start with zero-JS and add it surgically where the user experience genuinely demands it. This is exactly what Astro's island architecture enables -- 95% of the page is static HTML, and the one interactive widget gets hydrated.
Performance Benchmarks: JS vs Zero-JS
We've been tracking performance across our client projects. Here are real numbers from production sites we built in 2025-2026.
| Metric | Typical React SPA | Next.js (App Router) | Astro (Zero-JS) | Improvement |
|---|---|---|---|---|
| First Contentful Paint | 1.8s | 0.9s | 0.4s | 78% faster |
| Largest Contentful Paint | 2.5s | 1.3s | 0.6s | 76% faster |
| Time to Interactive | 3.2s | 1.8s | 0.4s | 87% faster |
| Total Blocking Time | 450ms | 180ms | 0ms | 100% reduction |
| JS Transfer Size | 280 KB | 105 KB | 0 KB | 100% reduction |
| Lighthouse Performance | 65-75 | 85-95 | 100 | -- |
| Core Web Vitals Pass Rate | 55% | 82% | 99% | -- |
These numbers matter for real business outcomes. Google has been increasingly transparent about CWV impact on search rankings. A 2025 study by Searchmetrics found that sites passing all Core Web Vitals had 24% higher average ranking positions than those failing. And that gap is widening.
For our clients, we've seen measurable improvements: one e-commerce brand saw a 15% increase in organic traffic after migrating from a React SPA to an Astro-based storefront with selective hydration. Their headless CMS architecture stayed the same -- we just changed how the frontend consumed and rendered content.
Building a Progressive Enhancement Strategy
Here's the practical playbook we follow:
Step 1: Audit Your JavaScript
Before you build anything new, look at what JavaScript you're currently shipping and ask: does this need to run on the client?
# Quick way to check JS usage in Chrome DevTools
# Coverage tab → Reload → See how much JS actually executes
We routinely find that 40-60% of shipped JavaScript never executes on initial page load. It's dead code, unused polyfills, or features that haven't been triggered.
Step 2: Categorize Your Interactivity
Put every interactive feature in one of three buckets:
- Platform-native -- Can be done with HTML/CSS alone (use platform)
- Enhancement -- Works without JS, better with it (progressive enhancement)
- Requires JS -- Genuinely impossible without client-side code (ship it)
Be honest with yourself. Most things land in bucket 1 or 2.
Step 3: Choose the Right Framework
If you're building a content site, documentation, marketing pages, or a blog -- reach for Astro or Eleventy. Don't choose Next.js for a marketing site just because your team knows React. The architectural mismatch costs you performance.
If you're building an application with significant client-side interactivity, Next.js or SvelteKit with selective server rendering makes more sense. Use Server Components where possible and client components only where necessary.
We help teams make exactly these decisions -- take a look at our capabilities or get in touch if you want to talk through your specific situation.
Step 4: Test Without JavaScript
This is the step everyone skips. Disable JavaScript in your browser and navigate your site. Does it work? Can users:
- Read content? ✓
- Navigate between pages? ✓
- Submit forms? ✓
- Access critical information? ✓
If not, your enhancement strategy has holes.
Real-World Architecture for Zero-JS Sites
Let me share a concrete architecture we've used for several client projects:
┌─────────────────────────────────────────┐
│ CDN (Cloudflare) │
│ Static HTML/CSS assets │
├─────────────────────────────────────────┤
│ Astro SSG / SSR Layer │
│ Fetches content at build/request │
├─────────────────────────────────────────┤
│ Headless CMS │
│ (Sanity / Storyblok / Payload) │
├─────────────────────────────────────────┤
│ Form Handler Service │
│ (Cloudflare Workers / Resend) │
└─────────────────────────────────────────┘
Content lives in a headless CMS. Astro pulls it at build time (or request time for frequently updated content). The output is pure HTML and CSS, deployed to a CDN edge. Forms submit to a serverless function that handles validation and email delivery.
The entire frontend has zero JavaScript. The CMS gives content editors a great experience. Forms work without client-side code. Page transitions use cross-document View Transitions. It's fast, accessible, and resilient.
For sites that need selective interactivity -- say, a product configurator on one page -- we use Astro's island architecture to hydrate just that component. The rest of the site stays static.
This is the kind of architecture we build regularly. If you're curious about pricing for this approach, check our pricing page -- zero-JS sites are typically faster to build and cheaper to host.
FAQ
Is progressive enhancement still relevant in 2026?
More relevant than ever. With 95%+ browser support for features like the Popover API, CSS :has(), View Transitions, and <dialog>, the web platform can handle interactivity that previously required JavaScript. Progressive enhancement isn't a philosophy from the past -- it's a practical engineering strategy that results in faster, more resilient, and more accessible websites.
Can you build a full website with zero JavaScript?
Absolutely. Marketing sites, blogs, documentation, portfolios, and even e-commerce storefronts can be built with zero client-side JavaScript. Forms submit natively, navigation uses standard links (with View Transitions for polish), and interactive components like accordions, modals, and tooltips all have HTML/CSS-native solutions. The sites you can't build without JS are real-time apps, rich text editors, and complex data visualizations.
How does zero JavaScript affect SEO?
Positively, in almost every case. Search engines can index HTML content instantly without waiting for JavaScript execution. Core Web Vitals scores improve dramatically -- especially Total Blocking Time, which drops to zero. Google's ranking systems reward fast, accessible pages, and zero-JS sites consistently achieve higher Lighthouse scores and better CWV pass rates.
What's the best framework for zero-JavaScript websites in 2026?
Astro is the strongest choice for most zero-JS projects. It outputs zero JavaScript by default and lets you add client-side interactivity per-component when needed. Eleventy is another excellent option for simpler sites. Both have mature ecosystems, good documentation, and active communities. The choice between them usually comes down to whether you want component-based authoring (Astro) or template-based simplicity (Eleventy).
Do CSS-only interactive components work for accessibility?
Native HTML elements like <details>, <dialog>, and the Popover API are accessible by default -- they handle focus management, keyboard navigation, and ARIA semantics automatically. CSS-only patterns using checkbox hacks need more care: you should add appropriate ARIA roles and ensure keyboard operability. In general, native HTML solutions are more accessible than custom JavaScript implementations because browser vendors have done the accessibility work for you.
How do View Transitions work without JavaScript?
Cross-document View Transitions (Level 2 of the spec) work entirely through CSS. You add a @view-transition { navigation: auto; } rule, and the browser automatically creates animated transitions between page navigations. You can customize the animations with ::view-transition-old() and ::view-transition-new() pseudo-elements. No JavaScript required. Chrome, Edge, and Safari support this in 2026, with Firefox support expected soon.
What percentage of users have JavaScript disabled?
Only about 1-2% of users actively disable JavaScript. But that's not the point. JavaScript fails for many more users than that -- flaky connections, corporate firewalls, browser extensions, CDN outages, and parsing errors all cause JS failures. The UK Government Digital Service found that 1.1% of users weren't getting JavaScript enhancements despite having JS enabled. Progressive enhancement protects all these users.
Can I use a headless CMS with a zero-JavaScript frontend?
Yes, and it's one of the best combinations. The CMS provides a rich editing experience for content teams, while the frontend (built with Astro or Eleventy) consumes content at build time via API and outputs pure HTML/CSS. The CMS JavaScript runs in the editor's browser, not your visitors' browsers. This decoupling gives you the best of both worlds: great authoring experience and zero-JS performance for end users.