Skip to content

intent-click

Detect intentional clicks vs accidental taps on touchscreens.

Adaptive threshold, ghost click suppression, scroll intent detection.

Features

  • Zero runtime dependencies
  • ~800 bytes gzip
  • ESM-only, TypeScript-first
  • SSR-safe
  • Works with React, Vue, Svelte, Solid, Angular, vanilla JS
  • Adaptive threshold based on devicePixelRatio
  • Ghost click suppression (iOS Safari 300ms)
  • Scroll intent detection
  • Long press discrimination
  • Concurrent pointer rejection

Quick Start

typescript
import { useIntentClick } from 'intent-click/react';

// React
const ref = useIntentClick<HTMLButtonElement>((event) => {
    // Guaranteed: this is an INTENTIONAL click
    deleteAccount();
}, { threshold: 'auto' });

<button ref={ref}>Delete Account</button>
typescript
// Vanilla JS
import { onIntentClick } from 'intent-click/vanilla';

const off = onIntentClick(buttonEl, () => {
    deleteAccount();
}, { threshold: 'auto' });

Framework Adapters

Why?

On touchscreens, accidental taps are common:

  • User scrolls, finger lands on a button
  • Drag gesture slightly moved, but browser still fires click
  • iOS Safari ghost clicks 300ms after touchend

Standard solutions don't work:

  • e.preventDefault() — breaks scrolling
  • touch-action: manipulation — doesn't solve drag-vs-click
  • fastclick — deprecated, only solved 300ms delay

intent-click distinguishes intentional clicks from:

  • Drag gestures (movement > threshold)
  • Scroll intents (vertical movement)
  • Ghost clicks (same coords within 400ms)
  • Long presses (>500ms without movement)
  • Concurrent pointers (pinch-zoom started)

How It Works

  1. Adaptive Threshold: 4px * devicePixelRatio on mouse devices, 6px * devicePixelRatio on touch devices
  2. Movement Tracking: PointerEvent pipeline tracks movementX / movementY
  3. Scroll Intent: If vertical movement > 10px and ratio < 0.5, it's a scroll
  4. Ghost Click Suppression: 400ms dead zone on coords ±5px
  5. Long Press: >500ms without movement triggers onLongPress
  6. SSR-Safe: Returns null on server, hydrates in browser

Documentation

Support

See Funding for support options.

License

MIT