Device Capabilities: Detect Features, Not Stereotypes
Modern devices combine many capabilities: touch input, precise pointers, keyboards, dense displays, motion preferences and browser-specific APIs.
- Author
- absequ
- Published
- Updated
- Category
- Responsive design
- device technologies
- responsive design
- practical guide
Modern devices combine many capabilities: touch input, precise pointers, keyboards, dense displays, motion preferences and browser-specific APIs. A device label cannot reliably predict all of them. Capability-based design asks which feature a task actually needs and provides a useful baseline when that feature is absent or permission is denied.
On this page
The mental model
Feature presence and feature availability are different. A browser may expose the Clipboard API but reject an operation because the context or permission is unsuitable. Installation support may require a browser-generated event and an explicit user gesture. Treat asynchronous rejection as a normal branch, not as an impossible exception.
Input capabilities can coexist. A touchscreen laptop may also have a mouse, and an assistive device may generate keyboard-like events. Media queries about a primary pointer describe a capability at a moment in time; they do not identify a person or prove a single interaction model.
A practical workflow
Build the essential task with broadly available semantics first. Add optional capabilities when detected and communicate fallback actions clearly. If copying fails, keep the canonical text selectable. If programmatic installation is unavailable, preserve accurate manual instructions rather than displaying an install button that cannot work.
Subscribe to capability or lifecycle events only where the feature needs them. Remove listeners on unmount and handle events that arrive before an asynchronous operation finishes. Keep the server's initial render and the client's first render consistent; browser-only detection should not produce a hydration mismatch.
Worked example
async function copyPublicLink(link) {
try {
await navigator.clipboard.writeText(link);
return { state: 'copied' };
} catch {
return { state: 'manual', instruction: 'Select and copy the link.' };
}
}
// Call from a user action and render the result in an accessible status region.The function does not infer success merely because navigator.clipboard exists. It waits for the operation and produces a bounded result for the interface. A real sharing feature should choose its canonical URL deliberately; using location.href can accidentally share localhost, tracking parameters or a transient authenticated route.
Separate presentation decisions from business authorization. A missing local API should change the available interaction, not the user's server permissions. Browser detection also must not be used as a security boundary: user agents and client-side properties are controllable by the caller.
Failure modes and security
Fine-grained device information can contribute to fingerprinting. Collect only what the feature needs, and avoid transmitting capabilities simply because they are easy to read. Permission prompts should follow a clear user action and explanation, not appear repeatedly on page load.
Test both the supported and rejected paths using controlled browser APIs. A simulated event can verify application state handling, but does not prove that a real operating system installed an app. Report that limitation explicitly and retain a manual acceptance step for native integrations.
Practice and verification
Implement a share-link control with pending, success and failure feedback. Test missing APIs, denied permissions and keyboard activation without changing the host clipboard during automated tests. Then outline an installation control that waits for a browser offer and never prompts after installation. Explain which parts can be verified in a headless browser and which require a compatible interactive browser.
References
Clipboard writeText — MDN
Installation event — MDN
Pointer capabilities — MDN