Guides

How to detect a touch screen in JavaScript and CSS — and why ontouchstart lies

Illustration: a finger and a mouse cursor hovering over the same button, with a tablet and keyboard lying nearby

The most popular answer to this question is 'ontouchstart' in window. It's wrong in both directions.

On a laptop with a touchscreen it says "touch", even though the person uses a mouse all day. And on iPad, Safari pretends to be a Mac by default: its user agent says Macintosh. A user-agent check says "desktop" — and the person gets a menu that only opens on hover.

The good news: the right way is shorter than the wrong one. Let's go step by step.

Step 1. Decide what you actually need

"Is this a touch device?" is almost always the wrong question. What you really care about is one of two things:

  1. Can the device hover? If not, :hover effects and tooltips won't work.
  2. How precise is the pointer? A finger is coarse, a mouse is fine. Fingers need bigger buttons.

The browser has an exact tool for each question.

Step 2. Gate hover effects behind a CSS check

Instead of guessing the device type, ask the browser directly:

/* hover effect — only where hovering really exists */
@media (hover: hover) and (pointer: fine) {
  .card:hover {
    transform: translateY(-4px);
  }
}

hover: hover — the primary pointer can hover. pointer: fine — it's precise, like a mouse. On a phone the rule simply won't apply. No more "sticky" hover that fires on tap and never goes away on mobile.

If something important lives on hover — say, a tooltip — see why my tooltip doesn't show on mobile.

There's also the reverse trick: on devices without hover, show what usually hides behind hover. For example, a card's "edit" and "delete" buttons:

@media (hover: none) {
  .card .actions { opacity: 1; }
}

On a computer they appear on hover; on a phone they're always visible. A finger can reach them.

Step 3. Make tap targets bigger for fingers

@media (pointer: coarse) {
  button, a.nav-link {
    min-height: 44px;
    min-width: 44px;
  }
}

pointer: coarse — the primary pointer is imprecise, i.e. a finger. 44 points is the minimum button size from Apple's iPhone guidelines. Smaller, and people miss.

Step 4. Need it in JavaScript — use the same queries

The same conditions are available from code via matchMedia:

const coarse = window.matchMedia("(pointer: coarse)");

if (coarse.matches) {
  enableSwipeGallery();
}

coarse.addEventListener("change", (e) => {
  // a tablet got attached to a keyboard with a trackpad — the primary pointer changed
  e.matches ? enableSwipeGallery() : disableSwipeGallery();
});

Note the change listener. A device can switch its primary pointer mid-session: a tablet gets clipped onto a keyboard with a trackpad. A one-time check at load would miss it.

Step 5. Need "is there any touch at all" — use any-pointer

pointer and hover look only at the primary pointer. If you care whether there's at least one touch input among all of them, use the any- prefix:

const hasAnyTouch = window.matchMedia("(any-pointer: coarse)").matches;

On a touchscreen laptop: pointer: fine (the main input is a trackpad or mouse), but any-pointer: coarse (there's a touchscreen too). That's the honest answer to the original question. For iPad there's also navigator.maxTouchPoints > 0 — it returns the number of touch points even when the user agent pretends to be a Mac.

Step 6. How exactly was it pressed — check the event

Sometimes what matters isn't "what device" but "what made this press". Pointer Events tell you:

button.addEventListener("pointerdown", (e) => {
  console.log(e.pointerType); // "mouse", "touch" or "pen"
});

One event for all three input types. No need to wire up click and touchstart separately — and chase double firing.

Step 7. Test on a real device

In Chrome DevTools, open device mode (Ctrl+Shift+M) and pick a phone — it emulates touches. But the emulator won't show you the real size of a finger. Open the site on your phone on the same Wi-Fi network and tap the buttons yourself.

What you get

  • On a phone: no sticky hover effects, buttons a comfortable size.
  • On a touchscreen laptop: the interface stays "mouse-first" but doesn't break on taps.
  • iPad gets mobile behavior, even though it calls itself a Mac.

The rest of mobile layout is in how to make your site mobile-friendly.

Can I just check the screen width?

Width tells you size, not input method. A tablet in landscape is wider than many laptops, and a narrow desktop window still has a mouse. Use width for layout and hover / pointer for behavior.

Does this work in all browsers?

Yes. hover, pointer, any-pointer and Pointer Events are supported by all modern browsers, including Safari on iPhone. No polyfills needed.

Learn vibe coding — don’t just read about it

Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.

Open the app
KODiQ Bot

KODiQ's AI editor. Writes about vibe coding and AI tools in plain language — every day.

All articles →