Browser Feature Detection
A practical guide to browser feature detection in JavaScript, covering capability checks, progressive enhancement, CSS feature detection, API detection, fallbacks, browser sniffing and common mistakes.
Web applications run in many different browsers, operating systems and devices. Even when two browsers implement the same web standards, they may support different features, expose different APIs or have different levels of support for newer capabilities.
Browser feature detection is the practice of checking whether a particular capability is actually available before using it. Instead of asking which browser the user has, your code asks whether the feature it needs exists and can be used.
This approach is one of the foundations of resilient web development. It allows an application to use modern APIs when they are available while providing a fallback when they are not.
What Is Browser Feature Detection?
Browser feature detection is a technique for determining whether a browser supports a specific web platform feature. The feature might be a JavaScript API, a CSS capability, a browser API, a media feature or another capability exposed by the platform.
For example, code can check whether the browser exposes a particular API before attempting to call it.
if ("geolocation" in navigator) {
console.log("Geolocation is available");
} else {
console.log("Geolocation is not available");
}The important part is that the application checks the capability itself. It does not need to know whether the browser is Chrome, Firefox, Safari or another browser.
Feature Detection vs Browser Detection
Feature detection and browser detection answer different questions.
| Technique | Question |
|---|---|
| Feature detection | Does this environment support the capability I need? |
| Browser detection | Which browser or browser family is being used? |
| User-Agent parsing | What information can be inferred from the browser's User-Agent string? |
For application behavior, feature detection is usually more useful because browser names are only indirect indicators of capabilities.
Why Browser Name Is Not Enough
Knowing that a user is running a particular browser does not necessarily tell you whether a feature is available. Support can depend on the browser version, operating system, device, experimental settings and other implementation details.
A browser can also expose a feature but have limitations that differ from another implementation. Conversely, multiple browsers can support the same feature even though their internal implementations are completely different.
A capability check focuses directly on the condition that matters to the application.
The Basic JavaScript Pattern
Many JavaScript APIs can be detected by checking whether a property exists on a known global object.
if ("serviceWorker" in navigator) {
console.log("Service workers are supported");
}The in operator checks whether the property exists on the object or its prototype chain. This is often preferable to immediately accessing and calling an API that might not exist.
Checking for a Function
Some APIs are exposed as functions. In those cases, checking the type can be appropriate.
if (typeof window.requestAnimationFrame === "function") {
window.requestAnimationFrame(() => {
console.log("Animation frame requested");
});
}This pattern confirms that the property exists and is callable as a function.
Checking Nested APIs Safely
When an API is nested inside another object, you should avoid directly accessing a potentially missing intermediate property in environments where it may not exist.
if ("clipboard" in navigator && "writeText" in navigator.clipboard) {
console.log("Clipboard text writing is available");
}Optional chaining can also be useful when you need to access a nested property without throwing an error because an intermediate object is undefined.
if (typeof navigator.clipboard?.writeText === "function") {
console.log("Clipboard API is available");
}Feature Detection for Browser APIs
JavaScript feature detection is particularly useful for Web APIs. Browsers expose APIs through objects such as window, document, navigator, screen and other platform interfaces.
| Capability | Example check |
|---|---|
| Geolocation | "geolocation" in navigator |
| Service workers | "serviceWorker" in navigator |
| Web Workers | "Worker" in window |
| IndexedDB | "indexedDB" in window |
| WebSocket | "WebSocket" in window |
| BroadcastChannel | "BroadcastChannel" in window |
The exact detection pattern should follow the API's documented availability and behavior rather than relying on a generic rule for every browser feature.
Feature Detection for Modern JavaScript Syntax
Feature detection is easiest for runtime APIs because JavaScript can inspect whether an object or function exists. It is different for language syntax.
If a browser cannot parse a piece of JavaScript syntax, the program may fail before your detection code can run. For syntax compatibility, transpilation, bundling or serving an appropriate version of the code is often more appropriate than runtime feature detection.
Feature Detection for JavaScript Methods
Methods added to built-in objects can often be checked before use.
if (typeof Array.prototype.flat === "function") {
const result = [1, [2, 3]].flat();
console.log(result);
}This can be useful when supporting environments where a modern built-in method may not exist. In older browser support scenarios, a polyfill can sometimes provide the missing functionality.
Feature Detection and Polyfills
A polyfill provides an implementation of a missing web platform feature, usually by adding compatible behavior to an older environment.
if (!("IntersectionObserver" in window)) {
// Load or initialize an appropriate fallback.
} else {
const observer = new IntersectionObserver(() => {
console.log("Element changed visibility");
});
}Modern projects often use build tools and targeted compatibility configurations instead of manually writing polyfill checks throughout application code. However, the underlying principle remains the same: determine the required capability and provide an alternative when necessary.
Feature Detection and Progressive Enhancement
Feature detection works especially well with progressive enhancement. The application starts with functionality that works in a broad range of environments and then enables additional capabilities when the browser supports them.
For example, a website can provide a normal navigation experience for everyone and enhance it with a modern browser API when that API is available.
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js");
}Users without service worker support can still use the basic website. The additional capability is an enhancement rather than a prerequisite for the entire application.
CSS Feature Detection
Feature detection is not limited to JavaScript. CSS provides the @supports rule for testing whether a browser understands a particular CSS declaration.
@supports (display: grid) {
.layout {
display: grid;
}
}This lets CSS progressively enhance a layout without requiring JavaScript to identify the browser.
Using CSS.supports()
JavaScript can also query CSS feature support through CSS.supports().
if (CSS.supports("display", "grid")) {
console.log("CSS Grid is supported");
}This can be useful when JavaScript behavior needs to depend on a CSS capability.
Detecting Media Features
The matchMedia API can be used to query media features such as viewport characteristics or user preferences.
const prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
);
if (prefersReducedMotion.matches) {
console.log("Reduced motion is preferred");
}This is a different type of detection from checking whether an API exists. Instead of asking whether the browser implements a capability, the application is asking what environment or user preference currently applies.
Feature Detection vs Device Detection
Device detection attempts to identify properties such as mobile versus desktop. Feature detection asks whether a specific capability is available.
| Question | Preferred technique |
|---|---|
| Can I use this JavaScript API? | Feature detection |
| Does this browser support this CSS property? | CSS feature detection |
| Does the user prefer reduced motion? | Media feature detection |
| What browser family is being reported? | User-Agent parsing |
| Is the viewport narrow? | Responsive CSS or media queries |
Why User-Agent Sniffing Is Fragile
A User-Agent string contains information that can help identify the browser and operating environment, but it is not a reliable substitute for capability detection.
const userAgent = navigator.userAgent;
if (userAgent.includes("SomeBrowser")) {
// Browser-specific behavior
}This code depends on a particular string representation rather than the capability the application actually needs. User-Agent strings can change, contain compatibility tokens and sometimes intentionally obscure or reduce identifying information.
When User-Agent Detection Is Still Useful
User-Agent information is not useless. There are cases where a server or analytics system genuinely needs to know what browser or client is making a request.
- Analytics and compatibility reporting.
- Debugging client-specific problems.
- Server-side logging.
- Security monitoring.
- Legacy systems with browser-specific requirements.
- Serving content based on client capabilities when feature negotiation is unavailable.
The important distinction is that identifying a client and deciding whether a particular web feature can be used are different tasks.
Browser Support Tables vs Runtime Detection
Browser compatibility databases are useful during development because they tell you whether a feature is expected to be supported by particular browser versions.
A Browser Support Checker or Browser Feature Lookup tool can help answer questions such as which browsers support an API and when support was introduced.
Runtime feature detection answers a different question: does the environment running this code actually provide the capability right now?
| Method | Best use |
|---|---|
| Browser compatibility data | Planning support requirements before development. |
| Browser Feature Lookup | Checking support for a specific platform feature. |
| Runtime feature detection | Choosing behavior while the application is running. |
| User-Agent parsing | Identifying the reported client environment. |
Feature Detection Does Not Guarantee Successful Use
An API existing on an object does not always mean every call will succeed. Some APIs require permissions, user interaction, secure contexts, specific document states or other runtime conditions.
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(
(position) => {
console.log(position.coords);
},
(error) => {
console.error("Geolocation failed:", error);
}
);
}The feature check establishes that the API exists. It does not guarantee that the user will grant permission or that the operation will succeed.
Support vs Availability
It is useful to distinguish between support and availability. A browser may support an API, but the API may be unavailable in the current context.
| Situation | Example |
|---|---|
| Feature unsupported | The browser does not expose the API. |
| Feature supported but restricted | The API requires a secure context. |
| Feature supported but permission denied | The user rejects a permission request. |
| Feature supported but operation fails | The API exists but the requested operation encounters an error. |
Good application code handles both capability detection and normal runtime errors.
Secure Context Requirements
Some modern browser APIs are available only in secure contexts, typically HTTPS pages, with limited exceptions for development environments such as localhost.
if (window.isSecureContext && "serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js");
}Checking the API itself can be enough in many cases, but additional environmental requirements may need to be checked when the API documentation specifies them.
Do Not Overuse Feature Detection
Feature detection is useful, but adding checks for every minor capability can make application code unnecessarily complicated.
If a browser feature is part of the baseline supported by the application, there may be no reason to branch around it. Feature detection is most valuable when there is a meaningful difference in behavior or a known fallback.
A Good Feature Detection Pattern
A practical feature detection implementation usually has three parts: detect the capability, use it when available and provide an alternative when it is not.
function saveData(data) {
if ("localStorage" in window) {
localStorage.setItem("data", JSON.stringify(data));
return;
}
// Use an alternative storage strategy.
}The fallback should be meaningful. If the feature is optional, the alternative might simply be to omit the enhancement rather than reproduce the entire API.
Avoid Detecting by Executing Unsupported Code
A common mistake is to execute a feature first and catch an exception afterward. This is sometimes necessary because an API can exist while an operation fails, but it should not replace straightforward capability checks.
if ("clipboard" in navigator && navigator.clipboard?.writeText) {
navigator.clipboard.writeText("Hello");
}The application can still catch errors because availability does not guarantee successful execution.
Detecting Feature Support in React
In React applications, browser feature detection should generally happen in browser-only code when the relevant API is unavailable during server rendering.
import { useEffect, useState } from "react";
export function ServiceWorkerStatus() {
const [supported, setSupported] = useState(false);
useEffect(() => {
setSupported("serviceWorker" in navigator);
}, []);
return <p>{supported ? "Supported" : "Not supported"}</p>;
}The exact implementation depends on the framework and rendering model. The important point is that browser globals such as window and navigator do not exist during normal server-side rendering.
Feature Detection in Next.js
Next.js applications can render components on the server, so browser APIs should not be accessed unconditionally during server rendering.
"use client";
import { useEffect, useState } from "react";
export default function FeatureStatus() {
const [supported, setSupported] = useState(false);
useEffect(() => {
setSupported("serviceWorker" in navigator);
}, []);
return <div>{supported ? "Available" : "Unavailable"}</div>;
}The useEffect callback runs in the browser, making it an appropriate place for checks that depend on browser-only globals.
Server-Side Feature Detection
Not all feature detection needs to happen in the browser. A server can inspect request headers and other information when deciding how to respond, but that is a different type of capability detection.
For example, HTTP request headers can communicate information about accepted content types, languages and other preferences. An HTTP Header Viewer can help inspect those headers when debugging requests.
Server-side detection should not be confused with knowing exactly what JavaScript APIs the browser exposes. The server generally cannot directly test a browser API without receiving some information from the client.
Browser Feature Detection and HTTP
Some capabilities are negotiated through HTTP rather than JavaScript. The Accept header, for example, tells the server which response media types a client indicates it can handle.
GET /resource HTTP/1.1
Host: example.com
Accept: application/json, text/htmlThis is a form of capability communication, but it should not be confused with JavaScript feature detection. HTTP negotiation and browser API detection operate at different layers of the web platform.
Feature Detection and Accessibility
Feature detection should not be used as an excuse to remove basic functionality from unsupported browsers. A robust web application should preserve essential interactions and accessibility wherever possible.
For example, a JavaScript enhancement can improve navigation or interaction while the underlying HTML remains usable without that enhancement.
Progressive Enhancement in Practice
A useful pattern is to build a working baseline first and then add optional enhancements when capabilities are available.
- Start with semantic HTML and basic functionality.
- Add CSS enhancements when the browser supports them.
- Use JavaScript enhancements when the required APIs exist.
- Provide fallbacks for important unsupported capabilities.
- Allow optional enhancements to fail without breaking the core experience.
Common Browser Feature Detection Mistakes
- Detecting the browser instead of the required capability.
- Assuming that a property existing means every operation will succeed.
- Accessing window or navigator during server-side rendering.
- Trying to detect unsupported JavaScript syntax at runtime.
- Using a User-Agent string as a substitute for feature detection.
- Adding unnecessary feature checks for capabilities already in the browser support baseline.
- Providing no fallback after detecting an unsupported feature.
- Ignoring permission and secure-context requirements.
- Testing only one browser and assuming identical behavior everywhere.
How to Test Feature Detection
Feature detection code should be tested in environments representing the browsers and devices that your application actually supports.
- Test the supported browser baseline.
- Test at least one environment where the feature is unavailable when practical.
- Test the fallback behavior.
- Test permission-denied scenarios for permission-based APIs.
- Test secure and insecure contexts when relevant.
- Test server rendering if the application uses SSR.
- Check browser developer tools for runtime errors.
Using Browser Support Data During Development
Runtime detection is not a replacement for researching browser compatibility before choosing an API. Development should start by understanding which browsers need to be supported.
A Browser Feature Lookup tool can help investigate a specific feature, while a Browser Support Checker can be used to compare support across browser environments. Once the application is running, runtime feature detection can provide the final decision for the current environment.
When Browser Detection Makes Sense
There are situations where the browser identity itself is relevant. For example, a development team might need to collect statistics about browser usage or investigate a bug affecting one browser implementation.
In those cases, User-Agent data can provide useful information. A User Agent Parser can turn a raw User-Agent string into structured browser and operating-system information.
The important distinction is that this information should not be used when a direct capability check can answer the actual application question more reliably.
A Practical Decision Process
- Identify the actual capability your application needs.
- Check whether the feature is part of your browser support baseline.
- Look up compatibility before implementing it.
- Use runtime feature detection when support can vary at runtime.
- Handle permissions and other environmental requirements.
- Provide a fallback for essential functionality.
- Keep optional enhancements optional.
- Use User-Agent information only when browser identity itself is relevant.
Frequently Asked Questions
What is browser feature detection?
Browser feature detection is the practice of checking whether a specific browser capability or API is available before using it. The application tests the feature itself instead of identifying the browser by name.
Why is feature detection better than browser detection?
Browser detection relies on the identity or User-Agent of the client, while feature detection checks the capability the application actually needs. This makes feature detection less dependent on browser names and version strings.
How do I check whether a browser supports a JavaScript API?
For many APIs, you can check whether a property exists on the relevant global object, such as using "serviceWorker" in navigator or checking whether a particular method is a function.
Can feature detection guarantee that an API call will work?
No. An API can exist while an operation still fails because of permissions, secure-context requirements, invalid input, unavailable resources or other runtime conditions. Capability detection should be combined with normal error handling.
Can CSS features be detected?
Yes. CSS provides @supports for stylesheet feature detection, and JavaScript can use CSS.supports() to test whether a browser supports a particular CSS declaration.
Should I use navigator.userAgent for feature detection?
Usually no. navigator.userAgent can provide useful information about the reported client, but it is generally less reliable for capability checks than directly testing the API or feature the application needs.
How does feature detection work with SSR?
Browser-only globals such as window and navigator should not be accessed unconditionally during server rendering. In React or Next.js, browser-dependent checks can be performed in client-side code such as an effect.
Helpful Browser Compatibility Tools
A Browser Feature Lookup tool can help investigate whether a particular web API or platform feature is supported. A Browser Support Checker is useful for comparing compatibility across browsers before choosing an implementation.
When browser identity is relevant, a User-Agent Parser can turn a User-Agent string into structured information, while a User-Agent Generator can produce representative User-Agent values for testing. An HTTP Header Viewer can also help inspect the request and response headers involved in browser-server communication.
Conclusion
Browser feature detection is a fundamental technique for building web applications that can adapt to different environments. Instead of assuming that a particular browser name guarantees a capability, the application checks whether the capability it actually needs is available.
JavaScript APIs can often be detected by checking properties and functions, while CSS provides mechanisms such as @supports and CSS.supports(). Media queries can detect environment preferences, and runtime checks can be combined with polyfills or alternative implementations when necessary.
Feature detection does not eliminate the need for compatibility research. Browser support data helps establish a sensible baseline, while runtime detection handles differences in the actual environment. User-Agent parsing still has legitimate uses, but browser identity should generally not be used as a substitute for capability detection.
The most robust approach is to build around capabilities rather than browser names: establish a browser support baseline, detect features when support can vary, handle runtime failures and permissions, and use progressive enhancement so that optional browser capabilities improve the experience without unnecessarily breaking the core application.