R3.16.1capability detectiondesign

Degrade by measured capability, not by device model

Aliases: UA sniffing · device model · hardwareConcurrency · saveData · feature detection

What it is

Whether to degrade asks whether this machine, right now, can carry the current task — not the model name it shipped with. A roster (a phone generation, a GPU, an OS version) is a marketing label. Two units of the same model can differ by background work, battery saver, thermal throttling, and other tabs. Capability detection reads observables: current frame rate, memory, logical cores, network type, saveData, prefers-reduced-motion. The UA string and the marketing name are not among them.

This is where the degrade switch takes its input — not what to cut first, and not whether functions may be cut. Animation is one measurable dimension, not the whole capability map.

Why it happens

A model name compresses factory specs into a word. Specs are a ceiling, not the budget currently available. Battery saver locks clocks, heat throttles them, other tabs contend for memory, and a low-end chip's ceiling was already against this task. Detection measures the budget: actual frame times over several frames, navigator.hardwareConcurrency, deviceMemory, Network Information downlink / saveData, the cost of one decode or one layout. If the budget is short, effects go off. UA sniffing treats "name → once-upon-a-time spec" as the current budget. Rosters lag, get spoofed, and are pierced by new models, and they give the same answer for a hot and a cold unit of that name.

Capability also changes mid-session. Frame rate is fine at the start and falls after three minutes of heat; the switch should be readable again, not locked to a model lookup at boot. User declarations (reduce motion, save data) are part of capability: they are not hardware measurements, but they are a direct observation of "how much this device will pay right now", which is more accurate than a model name.

Where it stops holding

Capability APIs lie or go missing: deviceMemory is coarse and rounded against fingerprinting; some network information is absent on Safari. Missing values need a conservative default, not flagship behaviour. Model rosters still help for crash stats and driver blacklists — "this chip has a known defect" — which is a defect, not a performance budget. Bots, prerender, and headless browsers do not represent users. Before the first paint there is no frame-rate sample; first-screen degradation can only use static signals (cores, memory, saveData, user declarations). Frame rate governs ongoing cost after that. A static reading page barely ever "cannot carry" the task; do not detect for detection's sake.

Applying it

  • Switch on cores, memory, saveData, reduced motion, and in-flight frame times. Do not maintain an "iPhone generation / Android family" roster.
  • Make detection re-readable in session: after heat or battery saver, allow a further step down, rather than judging once at start.
  • When an API is missing, take the conservative tier, not the highest.
  • How to check: on the same model, open the same page on high-performance plugged-in, on battery saver, and with a full background — tiers should split. Then a different name at the same capability should follow capability, not the name. Spoof the UA as a flagship and confirm unpaid-for effects do not turn on.

Related

  • Same group: R3.16.2 Order degradations by impact on task completion · R3.16.3 Keep functions reachable; reduce only presentation
  • Nearby: R3.08 Animation Cost · R3.04 Performance Budgets · K1.02 Screen Size and Density
  • Search terms: capability detection · saveData · hardwareConcurrency · user-agent sniffing

Cards in the same group

Quick Actions

Share

Share this page

ios_share

https://hci.top/en/handbook/R3.16.1