Progressive Enhancement Strategy
Last updated:
Ship a resilient baseline with native HTML and CSS — enhance with JavaScript only where it earns its place, without rebuilding the browser.
01 — Purpose
Resilient by default
Progressive enhancement ships a working baseline first — then layers capability. The browser already solves most interaction problems; rebuilding them in JavaScript is usually waste.
Networks fail. JavaScript errors. Extensions block scripts. Start with semantic HTML, server-rendered content, and native forms — then enhance where it genuinely helps.
Custom selects, dialog libraries, and menu systems must justify their cost in maintenance, accessibility, performance, and bundle size against what dialog, popover, links, and native validation already provide.
See semantic HTML, buttons vs links, and modal / dialog.
02 — Platform
Use the platform before you rebuild
Native-first does not mean never using frameworks — it means understanding what the platform already solves.
Modern CSS (Grid, Flexbox, layers, container queries, logical properties) removes many historical workarounds — see cascade layers and container queries.
- forms — validation, submission, accessible controls without a wrapper library
- dialogs and popovers — focus behaviour the platform owns
- navigation — real links, history, keyboard activation
- media, lazy loading, reduced motion, and semantic structure — before custom layers
When you replace platform behaviour, document why native options failed and prove keyboard, assistive technology, and mobile behaviour match user expectations.
03 — Principles
Enhancement improves — it does not invent
Enhancement should improve functionality — not invent it entirely.
- semantic HTML first — real links, buttons, forms, headings
- layered enhancement — CSS, then unobtrusive JS, then richer client behaviour
- resilient forms — submit and validate server-side; enhance client-side
- accessible defaults — keyboard and AT work before JS runs
04 — Practice
Good progressive enhancement
Core paths work without JavaScript; enhancement adds speed and polish.
- server-render or statically generate meaningful HTML — see static vs SSR vs CSR
- hydrate only interactive islands — see hydration and client interactivity
- defer hydration for non-critical widgets — see hydration and client interactivity
- enhance forms with validation and async UX — keep native submit as fallback
- test with JavaScript disabled on critical journeys — checkout, sign-in, search
05 — Avoid
Fragile JS-only interfaces
Custom replacements often break accessibility and performance first — delayed render, hydration cost, broken keyboard paths.
- JS-only interfaces — blank shell until bundle loads
- dependency-heavy rendering — entire app requires React/Vue to read a paragraph
- fragile interaction assumptions — div buttons, custom routing with no href fallback
- client-only data fetching for public content — SEO and no-JS users lose access
- skipping server validation — client-only checks that fail open
06 — Close
Baseline first, enhance second
If the page fails with JS off, the architecture is wrong for the content.
Define critical paths that must work without script. Enhance them — do not replace them. This site follows that model: static content, a button-controlled mobile menu with links in nav, and targeted client scripts for nav polish and syntax highlighting. Do not use details / summary for the primary site navigation — it breaks desktop layout.
See browser capability strategy, JS module architecture, hydration costs, and JavaScript standard.