Master progressive enhancement strategies to create resilient, accessible experiences that work everywhere while providing enhanced functionality on capable devices.
Progressive enhancement is a web design strategy that puts content and core functionality first, then layers on advanced features for devices and browsers that can handle them. This approach ensures your site works for everyone while providing optimal experiences for users with modern capabilities.
Unlike graceful degradation (which starts complex and removes features), progressive enhancement builds from a solid foundation upward. This creates more robust, accessible, and future-proof websites that perform well across the entire spectrum of devices and network conditions.
In this lesson, you'll learn to architect your responsive designs using progressive enhancement principles, ensuring your websites are fast, accessible, and resilient while still delivering cutting-edge experiences to capable devices.
Progressive enhancement follows a three-layer architecture that ensures core functionality while enabling advanced features.
The Three Layers:
Progressive Enhancement vs. Graceful Degradation:
1/* Progressive Enhancement Approach */
2/* Base experience - works everywhere */
3.navigation {
4 list-style: none;
5 padding: 0;
6}
7
8.nav-item {
9 display: block;
10 padding: 1rem;
11 border-bottom: 1px solid #eee;
12 color: #333;
13 text-decoration: none;
14}
15
16/* Enhanced experience - modern browsers */
17@supports (display: flex) {
18 .navigation {
19 display: flex;
20 flex-wrap: wrap;
21 }
22
23 .nav-item {
24 flex: 1;
25 border-bottom: none;
26 border-right: 1px solid #eee;
27 }
28}
29
30/* Advanced enhancement - latest features */
31@supports (display: grid) and (gap: 1rem) {
32 @media (min-width: 768px) {
33 .navigation {
34 display: grid;
35 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
36 gap: 1rem;
37 }
38
39 .nav-item {
40 border: 1px solid #eee;
41 border-radius: 8px;
42 }
43 }
44}Architecture Benefits:
Feature detection allows you to provide enhanced experiences only when browsers support specific features, creating robust fallbacks for older or limited browsers.
CSS Feature Detection with @supports:
1/* Baseline grid using flexbox */
2.content-grid {
3 display: flex;
4 flex-wrap: wrap;
5 margin: -1rem;
6}
7
8.content-item {
9 flex: 1 1 300px;
10 padding: 1rem;
11 min-height: 200px;
12}
13
14/* Enhanced grid with CSS Grid where supported */
15@supports (display: grid) {
16 .content-grid {
17 display: grid;
18 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
19 gap: 2rem;
20 margin: 0;
21 }
22
23 .content-item {
24 padding: 0;
25 min-height: auto;
26 }
27}
28
29/* Advanced features for modern browsers */
30@supports (backdrop-filter: blur(10px)) {
31 .glass-card {
32 background: rgba(255, 255, 255, 0.8);
33 backdrop-filter: blur(10px);
34 border: 1px solid rgba(255, 255, 255, 0.2);
35 }
36}
37
38/* Container queries for ultra-modern browsers */
39@supports (container-type: inline-size) {
40 .responsive-component {
41 container-type: inline-size;
42 }
43
44 @container (min-width: 300px) {
45 .component-content {
46 display: grid;
47 grid-template-columns: 1fr 2fr;
48 }
49 }
50}JavaScript Feature Detection:
1// Progressive enhancement for interactive features
2function enhanceNavigation() {
3 // Check for required APIs
4 if (!('querySelector' in document) || !('addEventListener' in window)) {
5 return; // Graceful degradation - basic HTML navigation works
6 }
7
8 const nav = document.querySelector('.mobile-nav');
9 const toggle = document.querySelector('.nav-toggle');
10
11 if (nav && toggle) {
12 // Enhance with JavaScript functionality
13 toggle.addEventListener('click', function() {
14 nav.classList.toggle('nav-open');
15 toggle.setAttribute('aria-expanded',
16 nav.classList.contains('nav-open'));
17 });
18
19 // Add keyboard support
20 toggle.addEventListener('keydown', function(e) {
21 if (e.key === 'Enter' || e.key === ' ') {
22 e.preventDefault();
23 this.click();
24 }
25 });
26 }
27}
28
29// Feature detection for advanced interactions
30if ('IntersectionObserver' in window) {
31 // Enhanced: Lazy loading and scroll animations
32 const observer = new IntersectionObserver(entries => {
33 entries.forEach(entry => {
34 if (entry.isIntersecting) {
35 entry.target.classList.add('animate-in');
36 }
37 });
38 });
39
40 document.querySelectorAll('.animate-on-scroll').forEach(el => {
41 observer.observe(el);
42 });
43}
44
45// Initialize enhancements
46document.addEventListener('DOMContentLoaded', enhanceNavigation);Modern Feature Detection Patterns:
1/* Detect hover capability */
2@media (hover: hover) {
3 .interactive-element:hover {
4 transform: translateY(-2px);
5 box-shadow: 0 4px 12px rgba(0,0,0,0.15);
6 }
7}
8
9/* Detect pointer precision */
10@media (pointer: fine) {
11 .precise-control {
12 padding: 0.25rem 0.5rem;
13 font-size: 0.875rem;
14 }
15}
16
17@media (pointer: coarse) {
18 .precise-control {
19 padding: 0.75rem 1rem;
20 font-size: 1rem;
21 min-height: 48px;
22 }
23}
24
25/* Detect reduced motion preference */
26@media (prefers-reduced-motion: reduce) {
27 * {
28 animation-duration: 0.01ms !important;
29 animation-iteration-count: 1 !important;
30 transition-duration: 0.01ms !important;
31 }
32}Testing Feature Detection:
Resilient interfaces work reliably across all devices and network conditions while providing enhanced experiences when possible.
HTML Foundation Principles:
1<!-- Semantic, accessible foundation -->
2<nav class="main-navigation" role="navigation" aria-label="Main">
3 <button class="nav-toggle" aria-controls="nav-menu" aria-expanded="false">
4 <span class="sr-only">Toggle navigation</span>
5 <span class="hamburger-icon"></span>
6 </button>
7
8 <ul class="nav-menu" id="nav-menu">
9 <li><a href="#home" class="nav-link">Home</a></li>
10 <li><a href="#about" class="nav-link">About</a></li>
11 <li><a href="#services" class="nav-link">Services</a></li>
12 <li><a href="#contact" class="nav-link">Contact</a></li>
13 </ul>
14</nav>
15
16<!-- Form with progressive enhancement -->
17<form class="contact-form" action="/contact" method="post" novalidate>
18 <div class="form-group">
19 <label for="name" class="form-label">Name (required)</label>
20 <input type="text" id="name" name="name" class="form-input"
21 required aria-describedby="name-error">
22 <span class="error-message" id="name-error" role="alert"></span>
23 </div>
24
25 <div class="form-group">
26 <label for="email" class="form-label">Email (required)</label>
27 <input type="email" id="email" name="email" class="form-input"
28 required aria-describedby="email-error">
29 <span class="error-message" id="email-error" role="alert"></span>
30 </div>
31
32 <button type="submit" class="submit-btn">
33 <span class="btn-text">Send Message</span>
34 <span class="btn-loading" hidden>Sending...</span>
35 </button>
36</form>CSS Resilience Patterns:
1/* Defensive CSS - handle edge cases */
2.card-layout {
3 /* Fallback for old browsers */
4 display: block;
5 width: 100%;
6 margin-bottom: 2rem;
7}
8
9/* Modern layout with fallbacks */
10@supports (display: flex) {
11 .card-layout {
12 display: flex;
13 flex-direction: column;
14 margin-bottom: 0;
15 }
16}
17
18/* Advanced layout for capable browsers */
19@supports (display: grid) {
20 .card-container {
21 display: grid;
22 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
23 gap: 2rem;
24 }
25
26 .card-layout {
27 margin-bottom: 0;
28 }
29}
30
31/* Handle content overflow gracefully */
32.content-text {
33 word-wrap: break-word;
34 overflow-wrap: break-word;
35 hyphens: auto;
36}
37
38/* Responsive images with fallbacks */
39.responsive-image {
40 max-width: 100%;
41 height: auto;
42 /* Fallback for browsers without object-fit */
43 width: 100%;
44}
45
46@supports (object-fit: cover) {
47 .responsive-image {
48 object-fit: cover;
49 width: 100%;
50 height: 200px;
51 }
52}
53
54/* Safe color schemes */
55.themed-content {
56 background-color: #ffffff;
57 color: #333333;
58}
59
60@supports (color-scheme: light dark) {
61 .themed-content {
62 color-scheme: light dark;
63 background-color: Canvas;
64 color: CanvasText;
65 }
66}
67
68@media (prefers-color-scheme: dark) {
69 .themed-content {
70 background-color: #1a1a1a;
71 color: #ffffff;
72 }
73}JavaScript Resilience:
1// Robust event handling
2function addEventListenerSafely(element, event, handler) {
3 if (element && typeof element.addEventListener === 'function') {
4 element.addEventListener(event, handler);
5 } else if (element && typeof element.attachEvent === 'function') {
6 element.attachEvent('on' + event, handler);
7 }
8}
9
10// Progressive form enhancement
11function enhanceForm(formSelector) {
12 const form = document.querySelector(formSelector);
13 if (!form) return;
14
15 // Basic validation works without JavaScript
16 // JavaScript adds real-time feedback
17 const inputs = form.querySelectorAll('input[required]');
18
19 inputs.forEach(input => {
20 input.addEventListener('blur', function() {
21 validateField(this);
22 });
23
24 input.addEventListener('input', function() {
25 clearFieldError(this);
26 });
27 });
28
29 form.addEventListener('submit', function(e) {
30 let isValid = true;
31
32 inputs.forEach(input => {
33 if (!validateField(input)) {
34 isValid = false;
35 }
36 });
37
38 if (!isValid) {
39 e.preventDefault();
40 // Focus first invalid field
41 const firstError = form.querySelector('.form-input.error');
42 if (firstError) firstError.focus();
43 }
44 });
45}
46
47function validateField(field) {
48 const errorElement = document.getElementById(field.getAttribute('aria-describedby'));
49 if (!errorElement) return true;
50
51 let isValid = true;
52 let errorMessage = '';
53
54 if (field.hasAttribute('required') && !field.value.trim()) {
55 isValid = false;
56 errorMessage = 'This field is required';
57 } else if (field.type === 'email' && field.value && !isValidEmail(field.value)) {
58 isValid = false;
59 errorMessage = 'Please enter a valid email address';
60 }
61
62 field.classList.toggle('error', !isValid);
63 errorElement.textContent = errorMessage;
64 errorElement.style.display = errorMessage ? 'block' : 'none';
65
66 return isValid;
67}
68
69function isValidEmail(email) {
70 return /^[^s@]+@[^s@]+.[^s@]+$/.test(email);
71}
72
73// Initialize with feature detection
74if (document.readyState === 'loading') {
75 document.addEventListener('DOMContentLoaded', () => {
76 enhanceForm('.contact-form');
77 });
78} else {
79 enhanceForm('.contact-form');
80}Resilience Testing Checklist:
Progressive enhancement should improve performance, not hinder it. Smart loading strategies ensure enhanced features don't slow down core functionality.
Critical Resource Prioritization:
1<!-- Critical CSS inline -->
2<head>
3 <style>
4 /* Critical above-the-fold styles inline */
5 body { font-family: system-ui; margin: 0; }
6 .header { background: #333; color: white; padding: 1rem; }
7 .main-content { max-width: 800px; margin: 0 auto; padding: 1rem; }
8 </style>
9
10 <!-- Non-critical CSS deferred -->
11 <link rel="preload" href="/css/enhanced.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
12 <noscript><link rel="stylesheet" href="/css/enhanced.css"></noscript>
13</head>Progressive Loading Strategies:
1/* Base layout - loads immediately */
2.image-gallery {
3 display: grid;
4 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
5 gap: 1rem;
6}
7
8.image-placeholder {
9 aspect-ratio: 16/9;
10 background: #f0f0f0;
11 display: flex;
12 align-items: center;
13 justify-content: center;
14 color: #666;
15}
16
17/* Enhanced loading states */
18.image-placeholder.loading::after {
19 content: '';
20 width: 40px;
21 height: 40px;
22 border: 3px solid #f0f0f0;
23 border-top-color: #666;
24 border-radius: 50%;
25 animation: spin 1s linear infinite;
26}
27
28@keyframes spin {
29 to { transform: rotate(360deg); }
30}
31
32/* Loaded image styles */
33.gallery-image {
34 width: 100%;
35 height: 100%;
36 object-fit: cover;
37 transition: opacity 0.3s ease;
38 opacity: 0;
39}
40
41.gallery-image.loaded {
42 opacity: 1;
43}
44
45/* Enhanced features for capable browsers */
46@media (min-width: 768px) {
47 .gallery-image:hover {
48 transform: scale(1.05);
49 transition: transform 0.3s ease, opacity 0.3s ease;
50 }
51}Smart JavaScript Loading:
1// Conditional feature loading
2function loadEnhancedFeatures() {
3 // Only load if device has sufficient resources
4 const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
5 const isSlowConnection = connection && (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g');
6
7 if (isSlowConnection) {
8 console.log('Slow connection detected, skipping heavy enhancements');
9 return;
10 }
11
12 // Check for sufficient device memory
13 if (navigator.deviceMemory && navigator.deviceMemory < 2) {
14 console.log('Limited memory device, using lightweight enhancements');
15 loadLightweightFeatures();
16 return;
17 }
18
19 // Load full enhancement suite
20 loadFullFeatures();
21}
22
23function loadLightweightFeatures() {
24 // Minimal enhancements for lower-end devices
25 import('./components/basic-interactions.js').then(module => {
26 module.initBasicInteractions();
27 });
28}
29
30function loadFullFeatures() {
31 // Full feature set for capable devices
32 Promise.all([
33 import('./components/advanced-gallery.js'),
34 import('./components/smooth-scroll.js'),
35 import('./components/lazy-loading.js')
36 ]).then(([gallery, scroll, lazy]) => {
37 gallery.initAdvancedGallery();
38 scroll.initSmoothScroll();
39 lazy.initLazyLoading();
40 });
41}
42
43// Intersection Observer for progressive loading
44function initProgressiveLoading() {
45 if (!('IntersectionObserver' in window)) {
46 // Fallback: load all images immediately
47 loadAllImages();
48 return;
49 }
50
51 const imageObserver = new IntersectionObserver((entries, observer) => {
52 entries.forEach(entry => {
53 if (entry.isIntersecting) {
54 const img = entry.target;
55 const src = img.dataset.src;
56
57 if (src) {
58 // Create new image to handle loading
59 const newImg = new Image();
60 newImg.onload = () => {
61 img.src = src;
62 img.classList.add('loaded');
63 img.removeAttribute('data-src');
64 };
65 newImg.src = src;
66 }
67
68 observer.unobserve(img);
69 }
70 });
71 });
72
73 // Observe all lazy images
74 document.querySelectorAll('img[data-src]').forEach(img => {
75 imageObserver.observe(img);
76 });
77}
78
79// Performance monitoring
80function monitorPerformanceImpact() {
81 if (!('PerformanceObserver' in window)) return;
82
83 const observer = new PerformanceObserver((list) => {
84 for (const entry of list.getEntries()) {
85 if (entry.duration > 100) {
86 console.warn('Slow operation detected:', entry.name, entry.duration);
87 }
88 }
89 });
90
91 observer.observe({ entryTypes: ['measure'] });
92}
93
94// Initialize based on device capabilities
95if (document.readyState === 'loading') {
96 document.addEventListener('DOMContentLoaded', () => {
97 initProgressiveLoading();
98 loadEnhancedFeatures();
99 monitorPerformanceImpact();
100 });
101} else {
102 initProgressiveLoading();
103 loadEnhancedFeatures();
104 monitorPerformanceImpact();
105}Resource Loading Optimization:
1/* Prevent layout shift during loading */
2.content-skeleton {
3 background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
4 background-size: 200% 100%;
5 animation: loading-shimmer 1.5s infinite;
6}
7
8@keyframes loading-shimmer {
9 0% { background-position: 200% 0; }
10 100% { background-position: -200% 0; }
11}
12
13/* Reduce motion for performance */
14@media (prefers-reduced-motion: reduce) {
15 .content-skeleton {
16 animation: none;
17 background: #f0f0f0;
18 }
19}
20
21/* Efficient animations using transform and opacity */
22.fade-in {
23 opacity: 0;
24 transform: translateY(20px);
25 transition: opacity 0.6s ease, transform 0.6s ease;
26}
27
28.fade-in.visible {
29 opacity: 1;
30 transform: translateY(0);
31}
32
33/* GPU acceleration for smooth animations */
34.accelerated {
35 will-change: transform;
36 transform: translateZ(0);
37}
38
39/* Reset will-change after animation */
40.accelerated.animation-complete {
41 will-change: auto;
42}Performance Monitoring:
Build a fully progressive enhanced interface that works without CSS or JavaScript while providing sophisticated features for modern browsers.
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Progressive Enhancement Mastery</title>
7 <style>
8 /* LAYER 1: BASE STYLES - ESSENTIAL FUNCTIONALITY */
9 * {
10 margin: 0;
11 padding: 0;
12 box-sizing: border-box;
13 }
14
15 body {
16 font-family: system-ui, -apple-system, sans-serif;
17 line-height: 1.6;
18 color: #333;
19 background: #fafafa;
20 }
21
22 /* Semantic HTML structure works without any CSS */
23 .sr-only {
24 position: absolute;
25 width: 1px;
26 height: 1px;
27 padding: 0;
28 margin: -1px;
29 overflow: hidden;
30 clip: rect(0, 0, 0, 0);
31 white-space: nowrap;
32 border: 0;
33 }
34
35 /* LAYER 2: BASIC PRESENTATION */
36 .container {
37 max-width: 1200px;
38 margin: 0 auto;
39 padding: 1rem;
40 }
41
42 .header {
43 background: #2563eb;
44 color: white;
45 padding: 1rem;
46 margin-bottom: 2rem;
47 }
48
49 .header h1 {
50 font-size: 1.5rem;
51 margin-bottom: 0.5rem;
52 }
53
54 /* Navigation without JavaScript dependency */
55 .nav {
56 background: white;
57 border-radius: 8px;
58 box-shadow: 0 2px 8px rgba(0,0,0,0.1);
59 margin-bottom: 2rem;
60 overflow: hidden;
61 }
62
63 .nav-toggle {
64 display: block;
65 width: 100%;
66 padding: 1rem;
67 background: white;
68 border: none;
69 text-align: left;
70 cursor: pointer;
71 font-size: 1rem;
72 border-bottom: 1px solid #e5e7eb;
73 }
74
75 .nav-menu {
76 list-style: none;
77 max-height: 0;
78 overflow: hidden;
79 transition: max-height 0.3s ease;
80 }
81
82 /* CSS-only toggle using :target */
83 .nav-menu:target {
84 max-height: 500px;
85 }
86
87 .nav-link {
88 display: block;
89 padding: 1rem;
90 color: #374151;
91 text-decoration: none;
92 border-bottom: 1px solid #f3f4f6;
93 transition: background-color 0.2s ease;
94 }
95
96 .nav-link:hover,
97 .nav-link:focus {
98 background: #f9fafb;
99 }
100
101 /* LAYER 3: ENHANCED FEATURES */
102 .content-grid {
103 display: block;
104 }
105
106 .card {
107 background: white;
108 border-radius: 8px;
109 padding: 1.5rem;
110 margin-bottom: 1.5rem;
111 box-shadow: 0 2px 8px rgba(0,0,0,0.1);
112 transition: transform 0.2s ease, box-shadow 0.2s ease;
113 }
114
115 .card h3 {
116 color: #1f2937;
117 margin-bottom: 1rem;
118 }
119
120 .card p {
121 color: #6b7280;
122 margin-bottom: 1rem;
123 }
124
125 /* Progressive form enhancement */
126 .form {
127 background: white;
128 border-radius: 8px;
129 padding: 2rem;
130 box-shadow: 0 2px 8px rgba(0,0,0,0.1);
131 }
132
133 .form-group {
134 margin-bottom: 1.5rem;
135 }
136
137 .form-label {
138 display: block;
139 margin-bottom: 0.5rem;
140 font-weight: 500;
141 color: #374151;
142 }
143
144 .form-input {
145 width: 100%;
146 padding: 0.75rem 1rem;
147 border: 2px solid #d1d5db;
148 border-radius: 6px;
149 font-size: 1rem;
150 transition: border-color 0.2s ease, box-shadow 0.2s ease;
151 }
152
153 .form-input:focus {
154 outline: none;
155 border-color: #2563eb;
156 box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
157 }
158
159 .form-input.error {
160 border-color: #dc2626;
161 }
162
163 .error-message {
164 display: none;
165 color: #dc2626;
166 font-size: 0.875rem;
167 margin-top: 0.25rem;
168 }
169
170 .error-message.show {
171 display: block;
172 }
173
174 .btn {
175 background: #2563eb;
176 color: white;
177 border: none;
178 padding: 0.75rem 1.5rem;
179 border-radius: 6px;
180 font-size: 1rem;
181 cursor: pointer;
182 transition: background-color 0.2s ease, transform 0.1s ease;
183 }
184
185 .btn:hover {
186 background: #1d4ed8;
187 }
188
189 .btn:active {
190 transform: translateY(1px);
191 }
192
193 .btn:disabled {
194 opacity: 0.5;
195 cursor: not-allowed;
196 }
197
198 .loading-spinner {
199 display: none;
200 width: 20px;
201 height: 20px;
202 border: 2px solid #ffffff;
203 border-top-color: transparent;
204 border-radius: 50%;
205 animation: spin 1s linear infinite;
206 }
207
208 @keyframes spin {
209 to { transform: rotate(360deg); }
210 }
211
212 /* LAYER 4: MODERN BROWSER ENHANCEMENTS */
213
214 /* Flexbox enhancement */
215 @supports (display: flex) {
216 .content-grid {
217 display: flex;
218 flex-wrap: wrap;
219 gap: 1.5rem;
220 margin: -0.75rem;
221 }
222
223 .card {
224 flex: 1 1 300px;
225 margin: 0;
226 }
227 }
228
229 /* Grid enhancement for modern browsers */
230 @supports (display: grid) {
231 @media (min-width: 768px) {
232 .content-grid {
233 display: grid;
234 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
235 gap: 1.5rem;
236 margin: 0;
237 }
238
239 .card {
240 flex: none;
241 }
242 }
243 }
244
245 /* Advanced interaction for capable devices */
246 @media (hover: hover) {
247 .card:hover {
248 transform: translateY(-2px);
249 box-shadow: 0 8px 25px rgba(0,0,0,0.15);
250 }
251 }
252
253 /* Backdrop filter support */
254 @supports (backdrop-filter: blur(10px)) {
255 .glass-header {
256 background: rgba(37, 99, 235, 0.9);
257 backdrop-filter: blur(10px);
258 border-bottom: 1px solid rgba(255, 255, 255, 0.2);
259 }
260 }
261
262 /* Container queries for ultra-modern browsers */
263 @supports (container-type: inline-size) {
264 .card-container {
265 container-type: inline-size;
266 }
267
268 @container (min-width: 350px) {
269 .card-content {
270 display: flex;
271 gap: 1rem;
272 }
273
274 .card-text {
275 flex: 1;
276 }
277 }
278 }
279
280 /* Reduced motion support */
281 @media (prefers-reduced-motion: reduce) {
282 * {
283 animation-duration: 0.01ms !important;
284 animation-iteration-count: 1 !important;
285 transition-duration: 0.01ms !important;
286 }
287 }
288
289 /* Dark mode support */
290 @media (prefers-color-scheme: dark) {
291 body {
292 background: #111827;
293 color: #f9fafb;
294 }
295
296 .header {
297 background: #1e40af;
298 }
299
300 .card, .nav, .form {
301 background: #1f2937;
302 border: 1px solid #374151;
303 }
304
305 .nav-link {
306 color: #d1d5db;
307 border-color: #374151;
308 }
309
310 .nav-link:hover {
311 background: #374151;
312 }
313
314 .form-input {
315 background: #1f2937;
316 border-color: #4b5563;
317 color: #f9fafb;
318 }
319 }
320
321 /* Enhanced navigation for larger screens */
322 @media (min-width: 768px) {
323 .nav-toggle {
324 display: none;
325 }
326
327 .nav-menu {
328 max-height: none;
329 display: flex;
330 overflow: visible;
331 }
332
333 .nav-link {
334 border-bottom: none;
335 border-right: 1px solid #e5e7eb;
336 }
337
338 .nav-link:last-child {
339 border-right: none;
340 }
341 }
342
343 /* Success states */
344 .success-message {
345 background: #dcfce7;
346 color: #166534;
347 padding: 1rem;
348 border-radius: 6px;
349 border-left: 4px solid #22c55e;
350 margin-bottom: 1rem;
351 }
352
353 @media (prefers-color-scheme: dark) {
354 .success-message {
355 background: #052e16;
356 color: #86efac;
357 border-color: #22c55e;
358 }
359 }
360 </style>
361</head>
362<body>
363 <!-- Semantic HTML foundation - works without CSS/JS -->
364 <header class="header glass-header">
365 <div class="container">
366 <h1>๐ Progressive Enhancement Demo</h1>
367 <p>This interface works at every layer of enhancement</p>
368 </div>
369 </header>
370
371 <div class="container">
372 <!-- Accessible navigation with CSS fallback -->
373 <nav class="nav" role="navigation" aria-label="Main navigation">
374 <button class="nav-toggle" aria-controls="nav-menu" aria-expanded="false"
375 onclick="toggleNavigation()">
376 <span>โฐ Menu</span>
377 <span class="sr-only">Toggle navigation</span>
378 </button>
379
380 <ul class="nav-menu" id="nav-menu">
381 <li><a href="#features" class="nav-link">Features</a></li>
382 <li><a href="#enhancement" class="nav-link">Enhancement</a></li>
383 <li><a href="#performance" class="nav-link">Performance</a></li>
384 <li><a href="#accessibility" class="nav-link">Accessibility</a></li>
385 <li><a href="#contact" class="nav-link">Contact</a></li>
386 </ul>
387 </nav>
388
389 <!-- Progressive enhancement demonstration -->
390 <main>
391 <h2>Progressive Enhancement Layers</h2>
392 <p>This demonstration shows how progressive enhancement creates resilient, accessible experiences.</p>
393
394 <div class="content-grid">
395 <article class="card card-container" id="features">
396 <div class="card-content">
397 <div class="card-text">
398 <h3>๐๏ธ Foundation Layer</h3>
399 <p>Semantic HTML ensures content is accessible to all users, devices, and assistive technologies.</p>
400 <p><strong>Works with:</strong> Any browser, any device, screen readers, search engines</p>
401 </div>
402 </div>
403 </article>
404
405 <article class="card card-container" id="enhancement">
406 <div class="card-content">
407 <div class="card-text">
408 <h3>๐จ Presentation Layer</h3>
409 <p>CSS adds visual design and responsive layouts while maintaining accessibility.</p>
410 <p><strong>Enhanced with:</strong> Flexbox, Grid, media queries, modern CSS features</p>
411 </div>
412 </div>
413 </article>
414
415 <article class="card card-container" id="performance">
416 <div class="card-content">
417 <div class="card-text">
418 <h3>โก Behavior Layer</h3>
419 <p>JavaScript adds interactive enhancements without breaking core functionality.</p>
420 <p><strong>Features:</strong> Form validation, smooth interactions, dynamic loading</p>
421 </div>
422 </div>
423 </article>
424
425 <article class="card card-container" id="accessibility">
426 <div class="card-content">
427 <div class="card-text">
428 <h3>โฟ Accessibility First</h3>
429 <p>Every layer maintains and improves accessibility for all users.</p>
430 <p><strong>Includes:</strong> ARIA labels, keyboard navigation, screen reader support</p>
431 </div>
432 </div>
433 </article>
434 </div>
435
436 <!-- Progressive form enhancement -->
437 <section id="contact">
438 <h2>Enhanced Contact Form</h2>
439 <p>This form works without JavaScript but provides enhanced validation with it.</p>
440
441 <form class="form" action="#" method="post" novalidate onsubmit="handleFormSubmit(event)">
442 <div class="form-group">
443 <label for="name" class="form-label">Name (required)</label>
444 <input type="text" id="name" name="name" class="form-input"
445 required aria-describedby="name-error" onblur="validateField(this)">
446 <span class="error-message" id="name-error" role="alert"></span>
447 </div>
448
449 <div class="form-group">
450 <label for="email" class="form-label">Email (required)</label>
451 <input type="email" id="email" name="email" class="form-input"
452 required aria-describedby="email-error" onblur="validateField(this)">
453 <span class="error-message" id="email-error" role="alert"></span>
454 </div>
455
456 <div class="form-group">
457 <label for="message" class="form-label">Message</label>
458 <textarea id="message" name="message" class="form-input"
459 rows="4" style="resize: vertical;"></textarea>
460 </div>
461
462 <button type="submit" class="btn" id="submit-btn">
463 <span class="btn-text">Send Message</span>
464 <span class="loading-spinner" id="loading-spinner"></span>
465 </button>
466 </form>
467
468 <div id="form-messages"></div>
469 </section>
470 </main>
471
472 <!-- Feature detection demonstration -->
473 <aside style="margin-top: 3rem; padding: 2rem; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
474 <h3>๐ Your Browser Capabilities</h3>
475 <div id="browser-features">
476 <p>Checking browser features...</p>
477 </div>
478 </aside>
479 </div>
480
481 <script>
482 // Progressive enhancement - JavaScript layer
483
484 // Feature detection
485 function detectFeatures() {
486 const features = {
487 'CSS Grid': CSS.supports('display', 'grid'),
488 'CSS Flexbox': CSS.supports('display', 'flex'),
489 'CSS Backdrop Filter': CSS.supports('backdrop-filter', 'blur(10px)'),
490 'CSS Container Queries': CSS.supports('container-type', 'inline-size'),
491 'Intersection Observer': 'IntersectionObserver' in window,
492 'Local Storage': 'localStorage' in window,
493 'Service Worker': 'serviceWorker' in navigator,
494 'Web Workers': 'Worker' in window,
495 'Fetch API': 'fetch' in window
496 };
497
498 const container = document.getElementById('browser-features');
499 if (container) {
500 let html = '<ul>';
501 for (const [feature, supported] of Object.entries(features)) {
502 html += `<li style="margin-bottom: 0.5rem;">
503 <span style="color: ${supported ? '#22c55e' : '#ef4444'};">
504 ${supported ? 'โ
' : 'โ'}
505 </span> ${feature}
506 </li>`;
507 }
508 html += '</ul>';
509 container.innerHTML = html;
510 }
511 }
512
513 // Navigation enhancement
514 function toggleNavigation() {
515 const menu = document.getElementById('nav-menu');
516 const toggle = document.querySelector('.nav-toggle');
517
518 if (menu && toggle) {
519 const isExpanded = menu.style.maxHeight && menu.style.maxHeight !== '0px';
520 menu.style.maxHeight = isExpanded ? '0' : '500px';
521 toggle.setAttribute('aria-expanded', !isExpanded);
522 }
523 }
524
525 // Form validation enhancement
526 function validateField(field) {
527 const errorElement = document.getElementById(field.getAttribute('aria-describedby'));
528 if (!errorElement) return true;
529
530 let isValid = true;
531 let errorMessage = '';
532
533 if (field.hasAttribute('required') && !field.value.trim()) {
534 isValid = false;
535 errorMessage = 'This field is required';
536 } else if (field.type === 'email' && field.value && !isValidEmail(field.value)) {
537 isValid = false;
538 errorMessage = 'Please enter a valid email address';
539 }
540
541 field.classList.toggle('error', !isValid);
542 errorElement.textContent = errorMessage;
543 errorElement.classList.toggle('show', !isValid);
544
545 return isValid;
546 }
547
548 function isValidEmail(email) {
549 return /^[^s@]+@[^s@]+.[^s@]+$/.test(email);
550 }
551
552 function handleFormSubmit(event) {
553 event.preventDefault();
554
555 const form = event.target;
556 const requiredFields = form.querySelectorAll('input[required]');
557 let isFormValid = true;
558
559 // Validate all required fields
560 requiredFields.forEach(field => {
561 if (!validateField(field)) {
562 isFormValid = false;
563 }
564 });
565
566 if (!isFormValid) {
567 // Focus first invalid field
568 const firstError = form.querySelector('.form-input.error');
569 if (firstError) firstError.focus();
570 return;
571 }
572
573 // Show loading state
574 const submitBtn = document.getElementById('submit-btn');
575 const btnText = submitBtn.querySelector('.btn-text');
576 const spinner = document.getElementById('loading-spinner');
577
578 submitBtn.disabled = true;
579 btnText.textContent = 'Sending...';
580 spinner.style.display = 'inline-block';
581
582 // Simulate form submission
583 setTimeout(() => {
584 // Show success message
585 const messages = document.getElementById('form-messages');
586 messages.innerHTML = `590`;
591
592 // Reset form
593 form.reset();
594
595 // Reset button
596 submitBtn.disabled = false;
597 btnText.textContent = 'Send Message';
598 spinner.style.display = 'none';
599
600 // Clear success message after delay
601 setTimeout(() => {
602 messages.innerHTML = '';
603 }, 5000);
604 }, 2000);
605 }
606
607 // Enhanced interactions with Intersection Observer
608 function initScrollAnimations() {
609 if (!('IntersectionObserver' in window)) return;
610
611 const observer = new IntersectionObserver((entries) => {
612 entries.forEach(entry => {
613 if (entry.isIntersecting) {
614 entry.target.style.opacity = '1';
615 entry.target.style.transform = 'translateY(0)';
616 }
617 });
618 }, {
619 threshold: 0.1,
620 rootMargin: '0px 0px -50px 0px'
621 });
622
623 // Animate cards on scroll
624 document.querySelectorAll('.card').forEach((card, index) => {
625 card.style.opacity = '0';
626 card.style.transform = 'translateY(20px)';
627 card.style.transition = `opacity 0.6s ease ${index * 0.1}s, transform 0.6s ease ${index * 0.1}s`;
628 observer.observe(card);
629 });
630 }
631
632 // Initialize enhancements based on capabilities
633 function initializeEnhancements() {
634 detectFeatures();
635 initScrollAnimations();
636
637 // Add keyboard navigation improvements
638 document.addEventListener('keydown', (e) => {
639 if (e.key === 'Escape') {
640 // Close mobile menu on Escape
641 const menu = document.getElementById('nav-menu');
642 const toggle = document.querySelector('.nav-toggle');
643 if (menu && toggle && menu.style.maxHeight !== '0px') {
644 toggleNavigation();
645 }
646 }
647 });
648 }
649
650 // Progressive initialization
651 if (document.readyState === 'loading') {
652 document.addEventListener('DOMContentLoaded', initializeEnhancements);
653 } else {
654 initializeEnhancements();
655 }
656 </script>
657</body>
658</html>This comprehensive progressive enhancement demonstration showcases how to build resilient interfaces that work everywhere while providing enhanced experiences for capable devices.
๐๏ธ Layer Architecture:
1. Content Foundation (HTML):
2. Presentation Layer (CSS):
3. Behavior Layer (JavaScript):
4. Advanced Enhancements:
๐ Feature Detection Patterns:
CSS Feature Detection:
1@supports (display: grid) {
2 .enhanced-layout { display: grid; }
3}
4
5@supports (backdrop-filter: blur(10px)) {
6 .glass-effect { backdrop-filter: blur(10px); }
7}JavaScript Capability Detection:
1if ('IntersectionObserver' in window) {
2 // Enhanced scroll animations
3}
4
5if (CSS.supports('container-type', 'inline-size')) {
6 // Container query enhancements
7}โก Performance Optimizations:
โฟ Accessibility Features:
๐ฑ Responsive Strategy:
๐งช Testing Approach:
This implementation demonstrates how progressive enhancement creates more robust, accessible, and performant web experiences while still delivering cutting-edge features to capable browsers and devices.
Excellent work mastering progressive enhancement! You now understand how to build resilient, accessible interfaces that work everywhere while providing enhanced experiences for capable devices. In our next lesson, we'll explore Device-Specific Optimizations, where you'll learn to detect and optimize for different device types, network conditions, and hardware capabilities. Get ready to create truly adaptive experiences that perform optimally across the entire spectrum of user contexts!
Your progress is saved in this browser, on this device only.