Detecting Touch Devices with JavaScript
Touch detection sounds simple until you’re staring at a Surface Pro, an iPad with a Magic Keyboard, or a Chromebook with a touchscreen. These hybrid devices break the assumption that “touch” and “mouse” are mutually exclusive—and that’s exactly where most detection approaches fall apart.
This article cuts through the noise: what actually works, what doesn’t, and when you shouldn’t bother detecting touch at all.
Key Takeaways
'ontouchstart' in windowis unreliable because browsers expose it inconsistently, even on devices without a touchscreen.navigator.maxTouchPoints > 0is the most dependable simple JavaScript check for reported touch capability across modern browsers.- CSS pointer media queries (
pointer,any-pointer,hover) handle most UI adaptations without any JavaScript. - The Pointer Events API and its
pointerTypeproperty let you detect the user’s current input method, which is far more useful on hybrid devices than a one-time check at page load.
Why 'ontouchstart' in window Isn’t Enough
For years, developers checked 'ontouchstart' in window as a quick way to detect touch capability. The problem is that browsers expose this property inconsistently. Some desktop browsers on Windows 8+ report it as true even without a touchscreen. Chrome has toggled its behavior across versions. It’s a leaky signal.
Relying on ontouchstart alone means you’re detecting browser behavior, not actual hardware capability.
navigator.maxTouchPoints: The More Reliable Baseline
The most dependable JavaScript property for detecting touch support today is navigator.maxTouchPoints. It returns the maximum number of simultaneous touch contact points the device supports. A value greater than zero means the hardware reports touch capability.
function hasTouchSupport() {
return navigator.maxTouchPoints > 0
}
This is part of the Pointer Events specification and is supported across all modern browsers—Chrome, Firefox, Safari, and Edge. It’s clean, readable, and doesn’t rely on event handler sniffing.
Note:
navigator.msMaxTouchPointswas the IE-era equivalent. You don’t need it unless you’re maintaining legacy code.
When CSS Pointer Media Queries Are the Better Tool
For most UI adaptations, you don’t need JavaScript at all. CSS pointer media queries let you adjust styling based on the precision of the primary input device.
/* Targets devices where the primary pointer is coarse (e.g., finger) */
@media (pointer: coarse) {
.btn {
min-height: 48px;
}
}
/* Targets any available pointer that is coarse, including secondary inputs */
@media (any-pointer: coarse) {
.tooltip {
display: none;
}
}
/* Targets devices where hover is not reliably available */
@media (hover: none) {
.dropdown:hover .menu {
display: none;
}
}
The difference between pointer and any-pointer matters on hybrid devices. pointer: coarse reflects only the primary input. any-pointer: coarse returns true if any connected input is coarse—useful when a device has both a mouse and a touchscreen.
Discover how at OpenReplay.com.
Pointer Events vs. Touch Events
Pointer Events are the modern, unified model for handling mouse, touch, and stylus input in JavaScript. Instead of maintaining separate touchstart and mousedown handlers, you write one:
element.addEventListener('pointerdown', (e) => {
if (e.pointerType === 'touch') {
// Handle touch input
} else if (e.pointerType === 'mouse') {
// Handle mouse input
}
})
The pointerType property tells you exactly what the user is doing right now—not what their device is theoretically capable of. This is the distinction that matters most on hybrid devices.
Legacy Touch Events (touchstart, touchmove, touchend) are still supported in most browsers but are not available in all environments and don’t cover mouse or stylus input. Prefer Pointer Events for new code.
The Hybrid Device Problem
A user on a touch-enabled laptop might start a session with a mouse, then reach up and tap the screen. Any detection you do at page load is already stale.
Rather than locking in a single detection result, listen for pointerdown events and read e.pointerType dynamically. This lets you adapt the UI based on what the user is actually doing, not what their device supports in theory.
What to Use and When
| Goal | Recommended Approach |
|---|---|
| Adjust button sizes or tap targets | CSS @media (pointer: coarse) |
| Suppress hover-only UI | CSS @media (hover: none) |
| Check touch hardware capability in JS | navigator.maxTouchPoints > 0 |
| Handle input events across all types | Pointer Events API |
| Detect current input type dynamically | event.pointerType on pointerdown |
Conclusion
Skip user agent sniffing entirely. For UI adjustments, CSS pointer media queries handle most cases without a single line of JavaScript. When you do need JavaScript, navigator.maxTouchPoints gives you a reliable hardware signal, and the Pointer Events API gives you real-time input context. Together, they cover the full range of modern devices—including the hybrid ones that make simple detection unreliable.
FAQs
Yes. navigator.maxTouchPoints is part of the Pointer Events specification and is supported in Chrome, Firefox, Safari, and Edge. It generally returns zero on devices that do not report touch support, and a positive integer on touch-capable devices. It is the most reliable single-property check available in JavaScript today for detecting touch support.
User agent strings are unreliable for determining input capabilities. They identify the browser and operating system, not the hardware. A Windows laptop and a Windows tablet can share the same user agent string despite having very different input methods. Feature detection through maxTouchPoints or CSS media queries is far more accurate.
Yes. The Pointer Events API provides a pointerType property on events like pointerdown. Its value is touch, mouse, or pen depending on the input being used at that moment. This is more useful than a one-time capability check, especially on hybrid devices where users switch between inputs.
The pointer media query targets only the primary input device. The any-pointer query returns true if any available input matches the condition. On a laptop with both a trackpad and a touchscreen, pointer coarse may be false because the trackpad is primary, but any-pointer coarse would be true because the touchscreen qualifies.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.