| name | progressive-enhancement |
| description | Load this skill when building any web feature, reviewing architecture decisions, or evaluating JavaScript dependencies. Under no circumstances build features that break completely when JavaScript is unavailable or fails. Absolutely always start with semantic HTML, layer CSS enhancements, and add JavaScript as the final, optional layer. Prioritize resilience and universal access over cutting-edge features.
|
Progressive Enhancement Accessibility Skill
Canonical source: examples/PROGRESSIVE_ENHANCEMENT_BEST_PRACTICES.md in mgifford/ACCESSIBILITY.md
This skill is derived from that file. When in doubt, the example is authoritative.
Apply these rules when building any web feature or reviewing architecture decisions.
Core Mandate
Start with a solid foundation that works for every user, then layer enhancements.
Every user — regardless of browser capability, network speed, assistive technology,
or JavaScript availability — must be able to access core content and complete core
tasks.
Progressive enhancement is also a sustainability practice. Pages that work without
JavaScript are dramatically lighter: fewer bytes transferred, less CPU spent on
parsing and executing scripts, and less energy consumed per page view. See
SUSTAINABILITY.md and the
Web Sustainability Guidelines.
Severity Scale (this skill)
| Level | Meaning |
|---|
| Critical | Core content or task inaccessible without JS/CSS |
| Serious | Core content accessible but significantly degraded without JS/CSS |
| Moderate | Enhancement degrades gracefully but with friction |
| Minor | Best-practice gap; marginal impact |
The Three Layers
Layer 1 — Semantic HTML (always required)
Failure here is Critical.
- All core content readable in plain HTML — no CSS or JS required
- Forms submittable with native browser behaviour
- Navigation functions as standard links
- Headings, lists, tables, and landmarks accurately reflect document structure
Layer 2 — CSS (enhance presentation)
Failure here is Moderate to Serious.
- External stylesheets that can be disabled without losing content
- Respect user preferences:
prefers-reduced-motion, prefers-color-scheme,
prefers-contrast, forced-colors
- Page remains usable if stylesheets fail to load
Layer 3 — JavaScript (enhance interactivity)
JS that gates core content or tasks is Critical.
- JS enhances; it does not gate access to core content or tasks
- Apply JS-dependent classes/behaviours from scripts, not static markup
- Handle script failure gracefully — the HTML layer must still work
- Use feature detection, not browser detection:
if ('fetch' in window && 'querySelector' in document) {
}
Layer 3 extension — Service Workers (offline capability)
Service Workers are a Layer 3 enhancement: they require JavaScript, are
registered by a script, and must degrade gracefully when unavailable (private
browsing, unsupported browsers, HTTPS not available).
When implemented, they extend both accessibility and sustainability:
- Accessibility: users on unreliable connections (common in rural areas,
low-income households, and developing regions) can continue to access
previously loaded content offline
- Sustainability: cached responses eliminate repeat network requests,
reducing data transfer and server energy per page view
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').catch((err) => {
console.warn('Service Worker registration failed:', err);
});
}
Offline fallback pattern:
const CACHE_NAME = 'v1';
const OFFLINE_URL = '/offline.html';
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) =>
cache.addAll([OFFLINE_URL, '/', '/styles.css'])
)
);
});
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).catch(() => caches.match(OFFLINE_URL))
);
}
});
The offline fallback page (/offline.html) must itself be a valid Layer 1
HTML document — semantic, readable without CSS or JS.
Service Worker caching strategies (cache-first, network-first, stale-while-
revalidate) should be chosen based on content update frequency. Avoid caching
strategies that serve stale content for content that changes frequently without
a cache-invalidation plan.
Critical: Core Content Must Not Require JavaScript
Rendering page content exclusively in JavaScript is Critical — users with
JS disabled, AT users encountering compatibility issues, and low-bandwidth users
are completely excluded.
- Deliver complete HTML from the server; hydrate interactivity in the browser
- Core content must be in the initial HTML response
- When using React/Vue/Angular/Svelte: configure SSR or static generation
- Avoid SPA patterns that require JS to render any visible content
Every byte of JavaScript that is not needed to access core content is unnecessary
energy consumption. Prefer server rendering — it transfers HTML once; a JS bundle
transfers code that must then be parsed, compiled, and executed on every visit.
Critical: Forms Must Work Without JavaScript
<form action="/search" method="get">
<label for="query">Search</label>
<input id="query" name="q" type="search">
<button type="submit">Search</button>
</form>
Enhance with JS for instant results, autocomplete, or inline validation —
while keeping the server-processed form as fallback.
A form that cannot be submitted without JS is Critical.
Critical: Navigation Must Work Without JavaScript
<nav aria-label="Main">
<ul>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
Enhance with JS for dropdowns or animated transitions.
Serious: Dynamic Content Must Have a Fallback Route
if ('fetch' in window) {
loadContentAsync(url);
} else {
}
Use aria-live regions only after confirming the base content is accessible
without them.
Moderate: CSS User Preferences Must Be Respected
prefers-reduced-motion, prefers-color-scheme, prefers-contrast, and
forced-colors are Layer 2 responsibilities. Ignoring them is Moderate at
minimum; ignoring prefers-reduced-motion for fast or flashing animations can
reach Serious.
What to Avoid
- Rendering page content exclusively in JavaScript — Critical
display:none / visibility:hidden on content required at the HTML layer — Critical
- Requiring JS to navigate between pages without a server-rendered fallback — Critical
user-scalable=no — Serious (prevents zoom for low-vision users)
- Assuming scripts will execute — always handle failure states
- Polyfills as a substitute for progressive enhancement (they patch features;
PE builds around their absence)
- Shipping large JS bundles for functionality that could be server-rendered —
this is both an accessibility risk and a sustainability cost
Definition of Done Checklist
Key WCAG Criteria
- 2.1.1 Keyboard (A) — native elements have built-in keyboard support
- 4.1.2 Name, Role, Value (A)
Note: WCAG 4.1.1 Parsing was removed in WCAG 2.2 (August 2023).
Modern browsers handle parsing errors uniformly. Valid semantic HTML remains
important as a progressive enhancement foundation, but it is no longer a
testable WCAG criterion. Do not cite it in audits or compliance statements.
References
Standards horizon: WCAG 3.0 is in development. No breaking changes to
progressive enhancement principles are anticipated.
Monitor: https://www.w3.org/TR/wcag-3.0/