User-Agent Detection in 2026: Feature Detect First, Parse Second

(Updated: July 16, 2026 ) User-Agent Client Hints browsers feature detection webdev

A snippet still circulating in 2026: if (/MSIE|Trident/.test(navigator.userAgent)). Another: map “Android” to a smaller image CDN path. Both rot. Browsers reduce User-Agent granularity, freeze legacy tokens, and ship behind identical engine strings. If your product branches on substrings, you are maintaining a museum.

What changed

  • UA reduction: less model-level detail by default
  • Frozen tokens: historical lies preserved so old sniffers keep “working” incorrectly
  • Client Hints: Sec-CH-UA-* headers and navigator.userAgentData for structured brands
  • Permissions and privacy reviews: high-entropy hints are fingerprinting surface

Capability APIs (IntersectionObserver, clipboard, container queries via CSS @supports) answer product questions better than “is this Chrome 126?”

Decision tree that ages better

  1. Can I detect the feature? Use @supports, in checks, or modest polyfills.
  2. Do I need device class (mobile vs desktop) for UX? Prefer responsive CSS and pointer/hover media queries over UA.
  3. Do I need analytics brands? Use Client Hints where available; fall back to UA parse knowing it is lossy.
  4. Do I need a bug workaround? Isolate behind a flagged sniff with a removal ticket and a browser version ceiling.
// Prefer
if ("serviceWorker" in navigator) { /* ... */ }

// Avoid as primary logic
if (/SamsungBrowser\/1[0-5]/.test(navigator.userAgent)) { /* ... */ }

Parsing for humans and tickets

Support engineers still need to read a raw UA from a bug report. A local UA display / parse helper turns an opaque string into browser, engine, OS, and device hints without sending the string to a third party. That is a diagnostics tool — not an architecture for feature gates.

When documenting bugs, paste the full UA and the parsed summary. Future you will thank present you when the reduced UA era makes two “Chrome 120” reports mean different platforms.

Client Hints in practice

Opt in with Accept-CH / Critical-CH as documented, request only the hints you need, and degrade when hints are missing (Firefox nuances, older browsers, privacy modes). Never block first paint on a hint round-trip if CSS alone can solve the layout. Collect less than “everything the API offers” — legal and privacy reviews increasingly ask whether UA-derived device graphs are necessary.

Bots, scrapers, and enterprise proxies

Server-side UA parsing still matters for allowing Googlebot, blocking obvious scrapers, and routing outdated browsers to a static support page. That is an operations concern with allowlists you update deliberately — not a reason for client-side React to choose components by sniffing. Keep bot tables server-side and versioned.

Be aware that spoofed UAs are trivial. Authorization must never key off UA alone. Use it as a weak signal among many. Enterprise proxies sometimes rewrite UA strings for “compatibility.” If only office-network users hit a weird branch, inspect the gateway before blaming the browser vendor.

Reduced UA and test matrices

Your Selenium grid might still show full strings while real users send reduced ones. Log both User-Agent and available Client Hints in staging to see the gap. Update browserslist targets from real analytics, not from a single developer’s Chrome version.

iOS remains the classic trap: Chrome, Edge, and Firefox on iOS are WebKit under the hood. Feature-detect; do not celebrate detecting the marketing name in the string. The same caution applies to “desktop mode” toggles on tablets that rewrite UA without changing the engine — your sniff thinks you gained Chrome features you never had.

Keep a living list of UA branches in the codebase with owners and expiry dates. Orphaned sniffers are how sites still special-case IE long after traffic died. If nobody will delete the branch next quarter, you probably do not need it now.

In 2026 the winning approach is boring: features first, responsive design second, structured hints for metrics, raw UA for forensics. Sniff strings only when you can name the bug, the browser, and the date you will delete the branch.