Browser Feature Detection APIs

1. CSS.supports() for CSS Feature Detection

Method Description
CSS.supports(property, value) Check if browser supports a CSS property-value pair.
CSS.supports(conditionText) Check if browser supports a CSS condition string.
@supports CSS rule Conditional CSS rules based on feature support.

Example: Check CSS property support

// Check individual property-value pairs
const supportsGrid = CSS.supports("display", "grid");
console.log("Grid support:", supportsGrid);
// Output: true (in modern browsers)

const supportsSubgrid = CSS.supports("grid-template-rows", "subgrid");
console.log("Subgrid support:", supportsSubgrid);
// Output: true/false depending on browser

const supportsGap = CSS.supports("gap", "1rem");
console.log("Gap support:", supportsGap);
// Output: true

// Check with condition strings
const supportsFlexGap = CSS.supports("(gap: 1rem) and (display: flex)");
console.log("Flex gap support:", supportsFlexGap);

const supportsContainerQueries = CSS.supports("container-type: inline-size");
console.log("Container queries:", supportsContainerQueries);

const supportsAspectRatio = CSS.supports("aspect-ratio: 16 / 9");
console.log("Aspect ratio:", supportsAspectRatio);

// Check vendor prefixes
const supportsSticky = CSS.supports("position", "sticky") ||
                       CSS.supports("position", "-webkit-sticky");
console.log("Sticky support:", supportsSticky);

// Complex conditions with OR/AND
const supportsModernLayout = CSS.supports(
  "(display: grid) or (display: flex)"
);
console.log("Modern layout:", supportsModernLayout);

// Check custom properties
const supportsCustomProps = CSS.supports("--custom-prop", "value");
console.log("Custom properties:", supportsCustomProps);
// Output: true (in modern browsers)

Example: Feature detection with fallbacks

// Grid with flexbox fallback
function applyLayout() {
  const container = document.querySelector(".container");
  
  if (CSS.supports("display", "grid")) {
    container.classList.add("grid-layout");
    console.log("Using CSS Grid");
  } else if (CSS.supports("display", "flex")) {
    container.classList.add("flex-layout");
    console.log("Using Flexbox fallback");
  } else {
    container.classList.add("float-layout");
    console.log("Using float fallback");
  }
}

// Check multiple features
const features = {
  grid: CSS.supports("display", "grid"),
  subgrid: CSS.supports("grid-template-rows", "subgrid"),
  containerQueries: CSS.supports("container-type", "inline-size"),
  aspectRatio: CSS.supports("aspect-ratio", "1"),
  gap: CSS.supports("gap", "1rem"),
  backdropFilter: CSS.supports("backdrop-filter", "blur(10px)"),
  scrollSnap: CSS.supports("scroll-snap-type", "y mandatory"),
  logicalProps: CSS.supports("margin-inline-start", "1rem")
};

console.log("Browser features:", features);

// Apply feature classes to html element
const html = document.documentElement;
Object.keys(features).forEach(feature => {
  if (features[feature]) {
    html.classList.add(`supports-${feature}`);
  } else {
    html.classList.add(`no-${feature}`);
  }
});

// Check color functions
const supportsOklch = CSS.supports("color", "oklch(0.5 0.2 180)");
const supportsP3 = CSS.supports("color", "color(display-p3 1 0 0)");

console.log("Modern color support:", {
  oklch: supportsOklch,
  displayP3: supportsP3
});

Example: CSS @supports in stylesheets

/* Basic @supports */
@supports (display: grid) {
  .container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 1rem;
  }
}

/* Fallback for older browsers */
@supports not (display: grid) {
  .container {
    display: flex;
    flex-wrap: wrap;
    margin: -0.5rem;
  }
  
  .container > * {
    margin: 0.5rem;
    flex: 1 1 250px;
  }
}

/* Complex conditions */
@supports (display: grid) and (gap: 1rem) {
  .modern-grid {
    display: grid;
    gap: 1rem;
  }
}

/* OR conditions */
@supports (position: sticky) or (position: -webkit-sticky) {
  .sticky-header {
    position: sticky;
    position: -webkit-sticky;
    top: 0;
  }
}

/* Container queries */
@supports (container-type: inline-size) {
  .card-container {
    container-type: inline-size;
  }
  
  @container (min-width: 400px) {
    .card {
      display: flex;
    }
  }
}

/* Modern color spaces */
@supports (color: oklch(0 0 0)) {
  .modern-colors {
    background: oklch(0.7 0.15 180);
  }
}

/* Backdrop filter with fallback */
.modal-backdrop {
  background: rgba(0, 0, 0, 0.5);
}

@supports (backdrop-filter: blur(10px)) {
  .modal-backdrop {
    background: rgba(0, 0, 0, 0.3);
    backdrop-filter: blur(10px);
  }
}
Note: CSS.supports() enables runtime CSS feature detection. Returns boolean indicating browser support. Use two-argument form for property-value pairs or single argument for condition strings. Combine with @supports in CSS for progressive enhancement.

2. Navigator.userAgent and Feature Sniffing

Property Description
navigator.userAgent User agent string identifying browser and OS.
navigator.vendor Browser vendor (e.g., "Google Inc.", "Apple Computer, Inc.").
navigator.platform Platform string (deprecated, use userAgentData).
navigator.userAgentData Structured user agent data (modern, limited browser support).
navigator.userAgentData.brands Array of browser brand information.
navigator.userAgentData.mobile Boolean indicating mobile device.
navigator.userAgentData.platform Operating system platform.
// Get user agent string
const ua = navigator.userAgent;
console.log("User Agent:", ua);
// Example output:
// "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36..."

// Detect browser (not recommended - use feature detection instead)
const isChrome = ua.includes("Chrome") && !ua.includes("Edg");
const isFirefox = ua.includes("Firefox");
const isSafari = ua.includes("Safari") && !ua.includes("Chrome");
const isEdge = ua.includes("Edg");

console.log("Browser detection:", {
  chrome: isChrome,
  firefox: isFirefox,
  safari: isSafari,
  edge: isEdge
});

// Detect OS (unreliable)
const isMac = ua.includes("Mac OS X");
const isWindows = ua.includes("Windows");
const isLinux = ua.includes("Linux");
const isIOS = /iPad|iPhone|iPod/.test(ua);
const isAndroid = ua.includes("Android");

console.log("OS detection:", {
  mac: isMac,
  windows: isWindows,
  linux: isLinux,
  iOS: isIOS,
  android: isAndroid
});

// Detect mobile (unreliable)
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua);
console.log("Is mobile:", isMobile);

// Better: Use feature detection instead
const hasTouchScreen = "ontouchstart" in window || navigator.maxTouchPoints > 0;
const isLikelyMobile = hasTouchScreen && window.innerWidth < 768;

console.log("Feature-based mobile detection:", isLikelyMobile);

Example: Modern User-Agent Client Hints API

// Check if User-Agent Client Hints available
if (navigator.userAgentData) {
  console.log("User-Agent Client Hints supported");
  
  // Basic info (available immediately)
  const { brands, mobile, platform } = navigator.userAgentData;
  
  console.log("Brands:", brands);
  // Output: [{brand: "Chromium", version: "120"}, {brand: "Google Chrome", version: "120"}]
  
  console.log("Is mobile:", mobile);
  // Output: false
  
  console.log("Platform:", platform);
  // Output: "macOS"
  
  // Get high-entropy values (requires permission)
  navigator.userAgentData.getHighEntropyValues([
    "platformVersion",
    "architecture",
    "model",
    "uaFullVersion",
    "fullVersionList"
  ]).then(values => {
    console.log("High entropy values:", values);
    // Output: {
    //   platformVersion: "13.0.0",
    //   architecture: "arm",
    //   model: "",
    //   uaFullVersion: "120.0.6099.109",
    //   fullVersionList: [...]
    // }
  });
  
} else {
  console.log("User-Agent Client Hints not supported");
  console.log("Fallback to user agent string:", navigator.userAgent);
}

// Better approach: Feature detection
function getBrowserCapabilities() {
  return {
    // Feature detection instead of UA sniffing
    supportsWebP: document.createElement("canvas")
      .toDataURL("image/webp").indexOf("data:image/webp") === 0,
    supportsWebGL: !!document.createElement("canvas").getContext("webgl"),
    supportsServiceWorker: "serviceWorker" in navigator,
    supportsWebRTC: "RTCPeerConnection" in window,
    supportsWebAssembly: typeof WebAssembly === "object",
    supportsES6Modules: "noModule" in document.createElement("script"),
    maxTouchPoints: navigator.maxTouchPoints || 0,
    deviceMemory: navigator.deviceMemory || "unknown",
    hardwareConcurrency: navigator.hardwareConcurrency || "unknown",
    connection: navigator.connection?.effectiveType || "unknown"
  };
}

console.log("Browser capabilities:", getBrowserCapabilities());
Note: Avoid user agent sniffing - use feature detection instead. User agent strings are unreliable and easily spoofed. Prefer navigator.userAgentData (Client Hints) for modern browsers. Always detect features, not browsers.
Warning: User agent sniffing is an anti-pattern. UA strings can be spoofed, change frequently, and don't reflect actual capabilities. Use feature detection with CSS.supports(), checking for API existence, or try/catch. Only use UA as last resort for critical bugs in specific browsers.

3. MediaCapabilities API for Media Format Support

Method Description
navigator.mediaCapabilities.decodingInfo(config) Check if browser can decode media configuration efficiently.
navigator.mediaCapabilities.encodingInfo(config) Check if browser can encode media configuration efficiently.
Configuration Properties Description
type "file" or "media-source" for decoding; "record" or "transmission" for encoding.
video Video configuration: contentType, width, height, bitrate, framerate.
audio Audio configuration: contentType, channels, bitrate, samplerate.

Example: Check video format support

// Check if browser can play specific video format
async function checkVideoSupport() {
  // H.264 configuration
  const h264Config = {
    type: "file",
    video: {
      contentType: "video/mp4; codecs=avc1.42E01E",
      width: 1920,
      height: 1080,
      bitrate: 5000000, // 5 Mbps
      framerate: 30
    }
  };
  
  // VP9 configuration
  const vp9Config = {
    type: "file",
    video: {
      contentType: "video/webm; codecs=vp9",
      width: 1920,
      height: 1080,
      bitrate: 5000000,
      framerate: 30
    }
  };
  
  // AV1 configuration
  const av1Config = {
    type: "file",
    video: {
      contentType: "video/mp4; codecs=av01.0.05M.08",
      width: 1920,
      height: 1080,
      bitrate: 3000000,
      framerate: 30
    }
  };
  
  try {
    const h264Info = await navigator.mediaCapabilities.decodingInfo(h264Config);
    const vp9Info = await navigator.mediaCapabilities.decodingInfo(vp9Config);
    const av1Info = await navigator.mediaCapabilities.decodingInfo(av1Config);
    
    console.log("H.264 support:", {
      supported: h264Info.supported,
      smooth: h264Info.smooth,
      powerEfficient: h264Info.powerEfficient
    });
    
    console.log("VP9 support:", {
      supported: vp9Info.supported,
      smooth: vp9Info.smooth,
      powerEfficient: vp9Info.powerEfficient
    });
    
    console.log("AV1 support:", {
      supported: av1Info.supported,
      smooth: av1Info.smooth,
      powerEfficient: av1Info.powerEfficient
    });
    
    // Choose best format
    if (av1Info.supported && av1Info.smooth && av1Info.powerEfficient) {
      return "av1";
    } else if (vp9Info.supported && vp9Info.smooth) {
      return "vp9";
    } else {
      return "h264";
    }
    
  } catch (error) {
    console.error("MediaCapabilities not supported:", error);
    // Fallback to canPlayType
    return fallbackFormatDetection();
  }
}

// Legacy fallback using canPlayType
function fallbackFormatDetection() {
  const video = document.createElement("video");
  
  if (video.canPlayType('video/mp4; codecs="avc1.42E01E"')) {
    return "h264";
  } else if (video.canPlayType('video/webm; codecs="vp9"')) {
    return "vp9";
  } else {
    return "unknown";
  }
}

// Usage
checkVideoSupport().then(format => {
  console.log("Selected video format:", format);
  loadVideoWithFormat(format);
});

Example: Check audio format and select best quality

// Check audio codec support
async function checkAudioSupport() {
  const formats = [
    {
      name: "opus",
      config: {
        type: "file",
        audio: {
          contentType: "audio/webm; codecs=opus",
          channels: 2,
          bitrate: 128000,
          samplerate: 48000
        }
      }
    },
    {
      name: "aac",
      config: {
        type: "file",
        audio: {
          contentType: "audio/mp4; codecs=mp4a.40.2",
          channels: 2,
          bitrate: 128000,
          samplerate: 44100
        }
      }
    },
    {
      name: "vorbis",
      config: {
        type: "file",
        audio: {
          contentType: "audio/ogg; codecs=vorbis",
          channels: 2,
          bitrate: 128000,
          samplerate: 44100
        }
      }
    }
  ];
  
  const results = {};
  
  for (const format of formats) {
    try {
      const info = await navigator.mediaCapabilities.decodingInfo(format.config);
      results[format.name] = info;
      console.log(`${format.name}:`, info);
    } catch (error) {
      console.error(`Failed to check ${format.name}:`, error);
      results[format.name] = { supported: false };
    }
  }
  
  return results;
}

// Adaptive media source selection
async function selectMediaSource(videoSources) {
  const capabilities = await Promise.all(
    videoSources.map(async (source) => {
      const info = await navigator.mediaCapabilities.decodingInfo({
        type: "media-source",
        video: {
          contentType: source.mimeType,
          width: source.width,
          height: source.height,
          bitrate: source.bitrate,
          framerate: source.framerate
        }
      });
      
      return { ...source, ...info };
    })
  );
  
  // Filter to supported, smooth, and power-efficient
  const ideal = capabilities.filter(c => 
    c.supported && c.smooth && c.powerEfficient
  );
  
  if (ideal.length > 0) {
    // Pick highest quality from ideal
    return ideal.reduce((best, current) => 
      current.bitrate > best.bitrate ? current : best
    );
  }
  
  // Fallback to any supported and smooth
  const acceptable = capabilities.filter(c => c.supported && c.smooth);
  
  if (acceptable.length > 0) {
    return acceptable[0];
  }
  
  // Last resort: any supported
  const anySupported = capabilities.find(c => c.supported);
  return anySupported || capabilities[0];
}

// Example usage
const sources = [
  {
    url: "video-4k-av1.mp4",
    mimeType: "video/mp4; codecs=av01.0.05M.08",
    width: 3840,
    height: 2160,
    bitrate: 10000000,
    framerate: 30
  },
  {
    url: "video-1080p-h264.mp4",
    mimeType: "video/mp4; codecs=avc1.42E01E",
    width: 1920,
    height: 1080,
    bitrate: 5000000,
    framerate: 30
  },
  {
    url: "video-720p-h264.mp4",
    mimeType: "video/mp4; codecs=avc1.42E01E",
    width: 1280,
    height: 720,
    bitrate: 2500000,
    framerate: 30
  }
];

selectMediaSource(sources).then(selected => {
  console.log("Selected source:", selected.url);
  videoElement.src = selected.url;
});
Note: MediaCapabilities API provides detailed media format support info. Returns supported, smooth, and powerEfficient booleans. Use to select optimal video/audio format based on device capabilities. More accurate than canPlayType().

4. Feature Policy and Permissions Policy APIs

API Description
document.featurePolicy Feature Policy API (deprecated, use Permissions Policy).
document.featurePolicy.allowsFeature(feature) Check if feature is allowed in current context.
Permissions-Policy HTTP header Control which features can be used (modern approach).
allow attribute on iframe Grant specific permissions to embedded content.

Example: Check feature policy

// Check if Feature Policy API is available
if (document.featurePolicy) {
  console.log("Feature Policy supported");
  
  // Check specific features
  const features = [
    "camera",
    "microphone",
    "geolocation",
    "payment",
    "autoplay",
    "fullscreen",
    "picture-in-picture",
    "accelerometer",
    "gyroscope",
    "magnetometer"
  ];
  
  features.forEach(feature => {
    const allowed = document.featurePolicy.allowsFeature(feature);
    console.log(`${feature}:`, allowed ? "allowed" : "blocked");
  });
  
  // Check with origin
  const allowedForOrigin = document.featurePolicy.allowsFeature(
    "geolocation",
    "https://example.com"
  );
  console.log("Geolocation for example.com:", allowedForOrigin);
  
  // Get all allowed features
  const allowedFeatures = document.featurePolicy.allowedFeatures();
  console.log("Allowed features:", allowedFeatures);
  
  // Get features list
  const allFeatures = document.featurePolicy.features();
  console.log("All features:", allFeatures);
  
} else {
  console.log("Feature Policy not supported");
}

// Modern approach: Check permissions before using
async function requestCameraWithPolicyCheck() {
  // First check if allowed by policy
  if (document.featurePolicy && !document.featurePolicy.allowsFeature("camera")) {
    console.error("Camera blocked by feature policy");
    return null;
  }
  
  // Then request permission
  try {
    const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    return stream;
  } catch (error) {
    console.error("Camera access denied:", error);
    return null;
  }
}

Example: Set Permissions Policy in HTTP headers and HTML

# HTTP Response Header (modern, recommended)
Permissions-Policy: 
  camera=(self "https://trusted.com"),
  microphone=(self),
  geolocation=(),
  payment=(self),
  autoplay=*,
  fullscreen=*,
  picture-in-picture=*

# Legacy Feature-Policy header (deprecated)
Feature-Policy: 
  camera 'self' https://trusted.com;
  microphone 'self';
  geolocation 'none';
  payment 'self'

Example: Use allow attribute on iframes

<!-- Grant specific permissions to iframe -->
<iframe
  src="https://example.com/embed"
  allow="camera; microphone; fullscreen; payment"
></iframe>

<!-- Allow all permissions (not recommended) -->
<iframe
  src="https://example.com/embed"
  allow="camera *; microphone *; geolocation *"
></iframe>

<!-- Explicitly block features -->
<iframe
  src="https://example.com/embed"
  allow="fullscreen"
  sandbox="allow-scripts allow-same-origin"
></iframe>

<!-- Combined with sandbox -->
<iframe
  src="https://example.com/payment"
  allow="payment"
  sandbox="allow-scripts allow-forms allow-same-origin"
></iframe>
Note: Permissions Policy (formerly Feature Policy) controls access to powerful features. Set via HTTP header or iframe allow attribute. Use document.featurePolicy.allowsFeature() to check before requesting permissions. Helps prevent unauthorized feature access.
Warning: Feature Policy API is deprecated - use Permissions Policy instead. Check browser support for specific policies. Some features like geolocation require both policy allowance AND user permission. Always handle graceful degradation.

5. Navigator Properties and Capability Detection

Property Description
navigator.onLine Boolean indicating network connectivity.
navigator.language Preferred language (e.g., "en-US").
navigator.languages Array of preferred languages.
navigator.hardwareConcurrency Number of logical processor cores.
navigator.deviceMemory Approximate device RAM in GB.
navigator.connection Network Information API object.
navigator.maxTouchPoints Maximum number of simultaneous touch points.
navigator.cookieEnabled Boolean indicating if cookies are enabled.
navigator.pdfViewerEnabled Boolean indicating if built-in PDF viewer available.

Example: Detect device capabilities

// Comprehensive capability detection
function getDeviceCapabilities() {
  const capabilities = {
    // Network
    online: navigator.onLine,
    connection: navigator.connection ? {
      effectiveType: navigator.connection.effectiveType,
      downlink: navigator.connection.downlink,
      rtt: navigator.connection.rtt,
      saveData: navigator.connection.saveData
    } : null,
    
    // Language
    language: navigator.language,
    languages: navigator.languages,
    
    // Hardware
    hardwareConcurrency: navigator.hardwareConcurrency,
    deviceMemory: navigator.deviceMemory,
    maxTouchPoints: navigator.maxTouchPoints,
    
    // Features
    cookieEnabled: navigator.cookieEnabled,
    pdfViewerEnabled: navigator.pdfViewerEnabled || false,
    
    // Screen
    screenSize: {
      width: window.screen.width,
      height: window.screen.height,
      availWidth: window.screen.availWidth,
      availHeight: window.screen.availHeight,
      colorDepth: window.screen.colorDepth,
      pixelDepth: window.screen.pixelDepth
    },
    
    // Viewport
    viewport: {
      width: window.innerWidth,
      height: window.innerHeight,
      devicePixelRatio: window.devicePixelRatio
    },
    
    // Storage
    storage: "storage" in navigator && "estimate" in navigator.storage,
    
    // Media
    mediaDevices: "mediaDevices" in navigator,
    
    // APIs
    serviceWorker: "serviceWorker" in navigator,
    geolocation: "geolocation" in navigator,
    bluetooth: "bluetooth" in navigator,
    usb: "usb" in navigator,
    webGL: !!document.createElement("canvas").getContext("webgl"),
    webGL2: !!document.createElement("canvas").getContext("webgl2"),
    webGPU: "gpu" in navigator,
    
    // Input
    pointerEvents: "PointerEvent" in window,
    touchEvents: "ontouchstart" in window
  };
  
  return capabilities;
}

// Use capabilities for adaptive loading
const caps = getDeviceCapabilities();
console.log("Device capabilities:", caps);

// Adapt based on capabilities
if (caps.deviceMemory && caps.deviceMemory < 4) {
  console.log("Low memory device - loading lightweight version");
  loadLightweightAssets();
}

if (caps.connection?.saveData) {
  console.log("Data saver mode active");
  disableAutoplayVideos();
  loadCompressedImages();
}

if (caps.hardwareConcurrency && caps.hardwareConcurrency < 4) {
  console.log("Limited CPU - reducing animations");
  reducedMotion();
}

// Monitor network changes
if (navigator.connection) {
  navigator.connection.addEventListener("change", () => {
    console.log("Connection changed:", {
      type: navigator.connection.effectiveType,
      downlink: navigator.connection.downlink,
      rtt: navigator.connection.rtt
    });
    
    adaptToConnection(navigator.connection);
  });
}

// Monitor online/offline
window.addEventListener("online", () => {
  console.log("Back online");
  syncOfflineData();
});

window.addEventListener("offline", () => {
  console.log("Gone offline");
  enableOfflineMode();
});

Example: Feature detection utilities

// Comprehensive feature detection
const Features = {
  // Storage APIs
  localStorage: (() => {
    try {
      const test = "__test__";
      localStorage.setItem(test, test);
      localStorage.removeItem(test);
      return true;
    } catch (e) {
      return false;
    }
  })(),
  
  indexedDB: "indexedDB" in window,
  
  // Worker APIs
  webWorker: "Worker" in window,
  serviceWorker: "serviceWorker" in navigator,
  sharedWorker: "SharedWorker" in window,
  
  // Modern APIs
  fetch: "fetch" in window,
  promise: "Promise" in window,
  intersectionObserver: "IntersectionObserver" in window,
  resizeObserver: "ResizeObserver" in window,
  mutationObserver: "MutationObserver" in window,
  
  // Media APIs
  webRTC: "RTCPeerConnection" in window,
  mediaRecorder: "MediaRecorder" in window,
  webAudio: "AudioContext" in window || "webkitAudioContext" in window,
  
  // Graphics
  canvas: (() => {
    const canvas = document.createElement("canvas");
    return !!(canvas.getContext && canvas.getContext("2d"));
  })(),
  
  webGL: (() => {
    const canvas = document.createElement("canvas");
    return !!(
      canvas.getContext("webgl") ||
      canvas.getContext("experimental-webgl")
    );
  })(),
  
  // Advanced features
  webAssembly: typeof WebAssembly === "object",
  webGL2: (() => {
    const canvas = document.createElement("canvas");
    return !!canvas.getContext("webgl2");
  })(),
  
  // Input
  touchEvents: "ontouchstart" in window,
  pointerEvents: "PointerEvent" in window,
  
  // Performance
  performanceObserver: "PerformanceObserver" in window,
  
  // Sensors
  deviceOrientation: "DeviceOrientationEvent" in window,
  deviceMotion: "DeviceMotionEvent" in window,
  
  // Permissions
  permissions: "permissions" in navigator,
  
  // Payment
  paymentRequest: "PaymentRequest" in window,
  
  // Credentials
  credentials: "credentials" in navigator,
  webAuthn: "credentials" in navigator && "create" in navigator.credentials,
  
  // Clipboard
  clipboardAPI: "clipboard" in navigator,
  
  // Share
  webShare: "share" in navigator
};

console.log("Feature support:", Features);

// Add feature classes to HTML
const html = document.documentElement;
Object.keys(Features).forEach(feature => {
  html.classList.add(Features[feature] ? feature : `no-${feature}`);
});

// Export for use in app
export default Features;
Note: Navigator properties provide device and browser capability info. Use hardwareConcurrency and deviceMemory for performance optimization. Check connection for adaptive loading. Feature detection more reliable than UA sniffing.

6. Modernizr Integration Patterns and Polyfills

Concept Description
Modernizr Feature detection library that adds CSS classes and JavaScript object.
Polyfill Code that implements missing browser features.
Progressive Enhancement Build base experience, then add features for capable browsers.
Graceful Degradation Build full experience, provide fallbacks for older browsers.

Example: Modernizr usage pattern

// After including Modernizr script
// Check features via JavaScript
if (Modernizr.webp) {
  console.log("WebP supported");
  loadWebPImages();
} else {
  console.log("WebP not supported");
  loadJPEGImages();
}

// Check multiple features
if (Modernizr.flexbox && Modernizr.cssgrid) {
  console.log("Modern layout supported");
  useModernLayout();
} else {
  console.log("Fallback to float layout");
  useFloatLayout();
}

// Load polyfills conditionally
if (!Modernizr.promises) {
  // Load Promise polyfill
  loadScript("https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js");
}

if (!Modernizr.fetch) {
  // Load fetch polyfill
  loadScript("https://cdn.jsdelivr.net/npm/whatwg-fetch@3/dist/fetch.umd.js");
}

// Helper to load scripts
function loadScript(src) {
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    script.src = src;
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script);
  });
}

Example: CSS with Modernizr classes

/* Modernizr adds classes to html element */

/* Use CSS Grid when available */
.cssgrid .container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;
}

/* Fallback to flexbox */
.no-cssgrid .container {
  display: flex;
  flex-wrap: wrap;
  margin: -0.5rem;
}

.no-cssgrid .container > * {
  margin: 0.5rem;
  flex: 1 1 250px;
}

/* WebP images */
.webp .hero {
  background-image: url('hero.webp');
}

.no-webp .hero {
  background-image: url('hero.jpg');
}

/* Object-fit support */
.objectfit img {
  width: 100%;
  height: 300px;
  object-fit: cover;
}

.no-objectfit img {
  width: 100%;
  height: auto;
}

/* Custom properties (CSS variables) */
.customproperties {
  --primary-color: #007bff;
  color: var(--primary-color);
}

.no-customproperties {
  color: #007bff;
}

/* Backdrop filter */
.backdropfilter .modal-backdrop {
  backdrop-filter: blur(10px);
  background: rgba(0, 0, 0, 0.3);
}

.no-backdropfilter .modal-backdrop {
  background: rgba(0, 0, 0, 0.7);
}

Example: Manual polyfill loading strategy

// Polyfill loader with feature detection
async function loadPolyfills() {
  const polyfills = [];
  
  // Check and load required polyfills
  if (!window.Promise) {
    polyfills.push(import("promise-polyfill"));
  }
  
  if (!window.fetch) {
    polyfills.push(import("whatwg-fetch"));
  }
  
  if (!window.IntersectionObserver) {
    polyfills.push(import("intersection-observer"));
  }
  
  if (!window.ResizeObserver) {
    polyfills.push(import("resize-observer-polyfill"));
  }
  
  if (!("remove" in Element.prototype)) {
    Element.prototype.remove = function() {
      if (this.parentNode) {
        this.parentNode.removeChild(this);
      }
    };
  }
  
  if (!("prepend" in Element.prototype)) {
    Element.prototype.prepend = function(...nodes) {
      const fragment = document.createDocumentFragment();
      nodes.forEach(node => {
        fragment.appendChild(
          node instanceof Node ? node : document.createTextNode(String(node))
        );
      });
      this.insertBefore(fragment, this.firstChild);
    };
  }
  
  // Wait for all polyfills to load
  await Promise.all(polyfills);
  
  console.log(`Loaded ${polyfills.length} polyfills`);
}

// Initialize app after polyfills
loadPolyfills().then(() => {
  console.log("Polyfills loaded, initializing app");
  initializeApp();
});

// Alternative: Polyfill.io service
// <script src="https://polyfill.io/v3/polyfill.min.js?features=default,fetch,IntersectionObserver"></script>

// Progressive enhancement example
function enhanceWithModernFeatures() {
  // Base functionality works everywhere
  const items = document.querySelectorAll(".item");
  
  // Enhance with Intersection Observer if available
  if ("IntersectionObserver" in window) {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          entry.target.classList.add("visible");
          observer.unobserve(entry.target);
        }
      });
    });
    
    items.forEach(item => observer.observe(item));
  } else {
    // Fallback: show all items immediately
    items.forEach(item => item.classList.add("visible"));
  }
}
Note: Modernizr adds feature detection classes to HTML element. Use for progressive enhancement in CSS and JavaScript. Load polyfills only when needed. Consider using dynamic imports or Polyfill.io service for automatic detection.
Warning: Don't over-polyfill - adds bloat and slows page load. Only polyfill features you actually use. Test polyfills thoroughly. Some features can't be polyfilled (e.g., CSS Grid in IE). Consider if supporting old browsers is worth the cost.

Browser Feature Detection Best Practices

  • Always use feature detection, never browser detection (user agent sniffing)
  • CSS.supports() is the best way to detect CSS feature support
  • Check for API existence before using: if ("fetch" in window)
  • Use MediaCapabilities API for optimal media format selection
  • Combine multiple detection methods for robust feature checking
  • Leverage navigator properties (deviceMemory, hardwareConcurrency) for adaptive experiences
  • Use Permissions Policy to control feature access in iframes
  • Load polyfills conditionally - only when features are missing
  • Prefer progressive enhancement over graceful degradation
  • Add feature classes to HTML element for CSS targeting
  • Test feature detection code in multiple browsers
  • Consider build-time feature detection with Modernizr custom builds
  • Monitor connection quality and adapt content loading accordingly