August 27, 2026

Feature Detection: Asking a Browser What It Can Do, Correctly

Not every browser supports every API, and guessing based on browser identity is a well-documented anti-pattern. Here's the actual, correct way to check what a browser can do before relying on it.

frontendjavascriptcompatibilityweb-development

Feature detection is the practice of directly checking whether a browser supports a specific capability before relying on it, rather than assuming support based on some other signal: a browser's reported identity, its release date, or general assumptions about "modern browsers." It's a small, precise technique with a correct and an incorrect way to do it, and the difference between the two shows up as real bugs in production for a meaningful share of visitors.

Why guessing is worse than it looks

The incorrect approach, inspecting a browser's user-agent string and branching logic on which browser it claims to be, has a specific, well-documented problem beyond being tedious to maintain: user-agent strings are self-reported, commonly spoofed or altered by extensions and privacy tools, and inconsistent across browser versions in ways that don't reliably correlate with actual feature support. MDN's own guide to feature detection is direct about this: user-agent sniffing is "a terrible practice that should be discouraged at all costs," precisely because it answers "what does this browser claim to be" rather than the question that actually matters: "does this specific capability work here."

This is relevant anywhere a page uses a capability that isn't universally supported across every browser a visitor might use: a newer CSS property, a JavaScript API added more recently than others, anything with partial or inconsistent implementation. Getting it wrong doesn't fail loudly. It fails as a silent broken experience for exactly the visitors on the browsers where the assumption was wrong, often without an error ever appearing in a log a developer would see.

The correct patterns

MDN's guide lays out a clear hierarchy of techniques, in order of preference depending on what's being checked:

  1. The in operator, for checking whether a top-level API entry point exists at all: "geolocation" in navigator.
  2. Create an element and check a property on it, for DOM- and element-specific features: !!document.createElement("canvas").getContext is MDN's own example. The double-negation coerces the result to an actual boolean rather than leaving it as a merely truthy or falsy value.
  3. CSS.supports(), or the @supports at-rule in stylesheets, for CSS feature detection. MDN calls this the "preferred method" for CSS specifically, since it requires no JavaScript at all.
if ("IntersectionObserver" in window) {
  // safe to use IntersectionObserver
}
 
if (CSS.supports("aspect-ratio", "1 / 1")) {
  // safe to rely on the aspect-ratio property
}

One subtlety worth knowing: the in operator checks the entire prototype chain, which means it can report true for a property that technically exists but isn't fully functional in a given browser. A partial or buggy implementation can still satisfy in. Where that distinction matters, the create-and-check pattern is more reliable, since it verifies the property is actually present on a real instance rather than just declared somewhere in the chain.

Optional chaining (?.) is the modern, ergonomic complement to both patterns. It doesn't replace feature detection (it doesn't tell a developer whether a capability exists, only prevents a crash if it doesn't), but it removes a lot of the manual guard-clause boilerplate that used to surround defensive checks, letting a feature-detection check focus on the actual yes/no question rather than also being responsible for not throwing.

Applying it

  • Ask "does this specific capability exist" directly, using the technique appropriate to what's being checked (top-level API, DOM feature, or CSS property). Never infer support from a browser's reported identity.
  • Prefer CSS.supports() for CSS features specifically; it requires no JavaScript and is the method MDN itself recommends first for that category.
  • Remember that in confirms presence, not necessarily full functionality. For anything where a partial implementation would be worse than no implementation, verify more directly (the create-and-check pattern) rather than trusting a bare in check.
  • Pair a feature-detection check with a real fallback, not just a guard clause that silently does nothing. The goal is graceful degradation, not just avoiding a crash.