A click that actually means “I meant it.” Intent press engine + multi-framework wrappers.
npm install nope-clickisDragging flags and timeouts.
pointercancel edge cases across browsers.
AbortController, minimal work per event.
bash
npm install nope-click
or
yarn add nope-click
or
pnpm add nope-click
`
Usage 🚀
$3
Use the useIntentPress hook.
`jsx
import { useState } from 'react'
import { useIntentPress } from 'nope-click/react'
const MyCard = () => {
const [count, setCount] = useState(0)
const press = useIntentPress(() => setCount((c) => c + 1))
return (
<>
onPointerDown={press.onPointerDown}
onClickCapture={press.onClickCapture}
style={{ padding: 12, border: '1px solid #ddd' }}
>
Intent presses: {count}
$3
Use the v-intent-press directive.
`html
Intent presses: {{ count }}
`
$3
Use the intentPress action.
`svelte
(count += 1), options: { clickGuard: true } }}>
Intent presses: {count}
`
$3
Use the intentPress directive.
Note for TypeScript users: You need to extend the JSX namespace to avoid type errors with use:.
`tsx
import { createSignal } from 'solid-js'
import { intentPress } from 'nope-click/solid'
// ⚠️ TypeScript only: Add this declaration to fix "Property 'use:intentPress' does not exist"
declare module "solid-js" {
namespace JSX {
interface Directives {
intentPress: boolean | { onIntent: (ev: any) => void; options?: object }
}
}
}
function App() {
const [count, setCount] = createSignal(0)
return (
setCount((c) => c + 1) }}>
Intent presses: {count()}
)
}
}
`
$3
Use the standalone IntentPressDirective.
`typescript
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IntentPressDirective } from 'nope-click/angular';
@Component({
selector: 'app-card',
standalone: true,
imports: [CommonModule, IntentPressDirective],
template:
})
export class CardComponent {
count = 0;
onIntent = () => { this.count++; };
}
`
$3
`js
import { createIntentPress } from 'nope-click/core'
const el = document.getElementById('card')
// Enable intent presses
const press = createIntentPress(() => {
console.log('intent!')
})
el.addEventListener('pointerdown', press.onPointerDown, { passive: true })
el.addEventListener('click', press.onClickCapture, { capture: true })
// Later, if you want to stop:
// press.destroy()
`
---
Configuration ⚙️
You can customize the duration and easing function.
`js
/// React
useIntentPress(onIntent, { slop: 10, clickGuard: true })
// Vue
// Svelte
`
| Option | Type | Default | Description |
|---|---|---|---|
| slop | number | auto | Movement allowed (px) before canceling as a drag. |
| maxPressMs | number | 0 | Max press duration; 0 disables timeout. |
| allowModified | boolean | false | Allow ctrl/alt/meta/shift modified presses. |
| allowTextSelection | boolean | false | If false, cancels when selection becomes a range. |
| allowNonPrimary | boolean | false | Allow non-primary mouse buttons. |
| preventDefault | boolean | false | Call preventDefault() on pointerdown when safe. |
| clickGuard | boolean | true | Suppress the trailing “ghost click” (capture phase). |
| enabled | boolean | true | Enable/disable without rewiring. |
How it works 🛠️
- pointerdown starts a “press transaction” (remember start point, selection snapshot, scroll parents).
- cancel when the user scrolls, drags past slop, selects text, or the browser cancels the pointer.
- pointerup commits: hit-test the release point (elementFromPoint), then call your handler.
- click capture guard (optional) suppresses the follow-up ghost click.
Support the project ❤️
> "We eliminated the isDragging spaghetti mess, saved your users from accidental scroll-clicks, and absorbed the cross-browser pointercancel nightmare. You saved dozens of hours not reinventing a wheel. Your donation is a fair trade for a rock-solid UI and weekends free from debugging."
If this library saved you time, please consider supporting the development:
1. Fiat (Cards/PayPal): via Boosty (one-time or monthly).
2. Crypto (USDT/TON/BTC/ETH): view wallet addresses on Telegram.
License
MIT
Keywords
nope-click, intent-press, intent-click, press, tap, touch, mobile, pointer-events, pointerdown, pointerup, pointercancel, click, click-capture, click-guard, ghost-click, accidental-click, scroll-release, drag, text-selection, hit-test, elementFromPoint, AbortController, passive-listeners, event-handling, interaction, ui, ux, zero-config, lightweight, tree-shakeable, react, vue, svelte, solid, angular, vanilla-js, typescript`