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. |
Example: User agent detection (not recommended)
// 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