Back

Detecting Touch Devices with JavaScript

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 window is unreliable because browsers expose it inconsistently, even on devices without a touchscreen.
  • navigator.maxTouchPoints > 0 is 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 pointerType property 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.

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.msMaxTouchPoints was 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.

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

GoalRecommended Approach
Adjust button sizes or tap targetsCSS @media (pointer: coarse)
Suppress hover-only UICSS @media (hover: none)
Check touch hardware capability in JSnavigator.maxTouchPoints > 0
Handle input events across all typesPointer Events API
Detect current input type dynamicallyevent.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.

OpenReplay