Skip to content
All essays
WebFebruary 3, 202410 min

Progressive Enhancement Strategies

Build web applications that work for everyone, everywhere, with progressive enhancement techniques

Ü
Ümit Uz
Mobile & Full Stack Developer

Progressive enhancement ensures your web app works for all users, regardless of their browser or device capabilities.

Core Principles

  1. 1Start with Basic HTML: Content must be accessible without JavaScript
  2. 2Layer on CSS: Enhance presentation and layout
  3. 3Add JavaScript: Enhance functionality progressively
  4. 4Test Extremes: Ensure functionality in minimal environments

Layer 1: Semantic HTML

Content-First Approach

html
<form action="/submit" method="POST">
  <label for="email">Email:</label>
  <input id="email" name="email" type="email" required>
  <button type="submit">Subscribe</button>
</form>

This works without JavaScript!

Semantic Elements

Use proper HTML5 elements:

  • <nav> for navigation
  • <article> for self-contained content
  • <section> for thematic grouping
  • <aside> for tangentially related content

Layer 2: CSS Enhancement

Mobile-First Base Styles

css
/* Base styles for all */
.container {
  width: 100%;
  padding: 1rem;
}

/* Enhanced for larger screens */
@media (min-width: 768px) {
  .container {
    max-width: 1200px;
    margin: 0 auto;
  }
}

Feature Queries

Use CSS features when available:

css
@supports (display: grid) {
  .grid {
    display: grid;
  }
}

@supports not (display: grid) {
  .grid {
    display: flex;
    flex-wrap: wrap;
  }
}

CSS Custom Properties with Fallbacks

css
.button {
  background: #0066cc; /* Fallback */
  background: var(--button-color, #0066cc);
}

Layer 3: JavaScript Enhancement

Feature Detection

Before using new APIs:

javascript
if ('IntersectionObserver' in window) {
  const observer = new IntersectionObserver(callback);
  // Use observer
} else {
  // Fallback: load all images immediately
}

Progressive Form Enhancement

javascript
// Base: HTML form works without JS
const form = document.querySelector('form');

// Enhance: AJAX submission
if ('fetch' in window) {
  form.addEventListener('submit', async (e) => {
    e.preventDefault();
    const formData = new FormData(form);

    const response = await fetch(form.action, {
      method: 'POST',
      body: formData
    });

    // Handle response
    showSuccessMessage();
  });
}

Lazy Loading Enhancements

javascript
// Base: Images load normally
<img src="photo.jpg" alt="Photo">

// Enhanced: Lazy load when supported
if ('IntersectionObserver' in window) {
  const images = document.querySelectorAll('img[data-src]');
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;
        observer.unobserve(img);
      }
    });
  });

  images.forEach(img => observer.observe(img));
}

Practical Examples

Responsive Images

html
<!-- Base: Single image -->
<img src="image-small.jpg" alt="Description">

<!-- Progressive: Multiple sources -->
<picture>
  <source srcset="image-large.webp" type="image/webp" media="(min-width: 1024px)">
  <source srcset="image-medium.webp" type="image/webp" media="(min-width: 768px)">
  <source srcset="image-small.jpg">
  <img src="image-small.jpg" alt="Description" loading="lazy">
</picture>

Offline Support

javascript
// Base: App works online
// Enhanced: Cache for offline access

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js')
      .then(reg => console.log('SW registered'))
      .catch(err => console.log('SW failed'));
  });
}

Geolocation Enhancement

javascript
// Base: Manual location input
<input type="text" name="location" placeholder="Enter your city">

// Enhanced: Auto-detect location
if ('geolocation' in navigator) {
  const button = document.createElement('button');
  button.textContent = 'Use my location';

  button.addEventListener('click', () => {
    navigator.geolocation.getCurrentPosition(
      (position) => {
        const { latitude, longitude } = position.coords;
        // Reverse geocode to get city
        updateLocationField(latitude, longitude);
      },
      (error) => {
        showError('Could not get location');
      }
    );
  });

  form.appendChild(button);
}

Testing Progressive Enhancement

Test Strategy

  1. 1Disable JavaScript and verify core functionality
  2. 2Test on slow networks (Chrome DevTools)
  3. 3Use older browsers via BrowserStack
  4. 4Test with screen readers
  5. 5Validate with WAVE axe DevTools

Lighthouse Testing

Target scores:

  • Performance: 90+
  • Accessibility: 100
  • Best Practices: 90+
  • SEO: 90+

Benefits

  1. 1Broader Audience: Works for everyone
  2. 2Better SEO: Search engines crawl content
  3. 3Faster Perceived Performance: Content loads quickly
  4. 4Graceful Degradation: Fallbacks for unsupported features
  5. 5Future-Proof: New browsers get enhancements

Conclusion

Progressive enhancement isn't about limiting features—it's about ensuring everyone gets a functional experience. Start with solid HTML/CSS, then enhance with JavaScript. Test thoroughly, and build apps that work everywhere.

Related essays

Next essay
CSS-in-JS Deep Dive: Styled Components vs Emotion