npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sharpbits/react-interaction-hooks

v0.3.0

Published

A collection of 63 React hooks for UI interactions — pointer, touch, keyboard, focus, scroll, resize, clipboard, device sensors, and more. Zero dependencies, tree-shakeable, lightweight, and blazing fast.

Downloads

1,519

Readme

@sharpbits/react-interaction-hooks

A collection of 63 React hooks for UI interactions — pointer, touch, keyboard, focus, scroll, resize, clipboard, device sensors, timing, and more. Zero dependencies, tree-shakeable, lightweight, and blazing fast.

npm license demo

Installation

npm install @sharpbits/react-interaction-hooks

Hooks

Pointer & Mouse

| Hook | Description | |------|-------------| | useOutsideClick | Detect clicks/touches outside an element | | useHover | Track hover state with optional delay | | useDrag | Track drag position and delta | | usePointerPosition | Track mouse position on the page | | useContextMenu | Intercept right-click on an element | | useMouseLeaveWindow | Detect when mouse exits the browser window | | useDoubleClick | Detect double-click on an element (mouse) | | usePointerLock | Lock/unlock pointer for raw mouse movement |

Touch

| Hook | Description | |------|-------------| | useSwipe | Detect swipe direction from touch | | useDoubleTap | Detect double-tap on touch devices | | usePinch | Detect pinch-to-zoom gesture | | useLongPress | Detect long press on mouse and touch |

Keyboard

| Hook | Description | |------|-------------| | useKeyPress | Track whether a specific key is held down | | useKeyCombo | Listen for keyboard shortcuts | | useArrowNavigation | Navigate a list with arrow keys | | useFocusTrap | Trap Tab focus inside an element | | useKeySequence | Detect ordered key sequences (Konami code style) |

Focus & Visibility

| Hook | Description | |------|-------------| | useFocusWithin | Detect focus inside an element | | useIntersectionObserver | Detect when an element enters/leaves the viewport | | useIdle | Detect user inactivity | | usePageVisibility | Detect when the browser tab is hidden | | useFocusReturn | Restore focus to the trigger element on unmount | | useTabFocus | Detect whether user is navigating via keyboard |

Scroll

| Hook | Description | |------|-------------| | useScrollPosition | Track scroll X/Y position | | useScrollDirection | Detect scroll direction (up/down) | | useScrollProgress | Scroll progress as a 0–1 value | | useScrollLock | Lock body scroll (iOS Safari safe) | | useScrollIntoView | Scroll an element into view programmatically | | useScrollSpy | Track which section is active based on scroll | | useInfiniteScroll | Trigger a callback when a sentinel element is visible |

Resize & DOM

| Hook | Description | |------|-------------| | useResizeObserver | Track element width/height changes | | useWindowSize | Track browser window dimensions | | useElementSize | Track rendered size of an element via ResizeObserver | | useElementPosition | Track viewport-relative position of an element | | useContainerQuery | Match element width against named breakpoints | | useMutationObserver | Observe DOM mutations on an element | | useFullscreen | Enter/exit fullscreen on an element |

Clipboard & Media

| Hook | Description | |------|-------------| | useCopyToClipboard | Copy text to clipboard | | useMediaQuery | Subscribe to a CSS media query | | useTextSelection | Track selected text and its bounding rect | | useDropZone | Accept file drag-and-drop on an element | | usePaste | Listen for paste events anywhere on the page | | useShare | Trigger the native Web Share dialog | | usePermission | Query browser permission state | | useNotification | Request permission and send desktop notifications | | useReducedMotion | Detect prefers-reduced-motion preference | | useColorScheme | Detect prefers-color-scheme (dark/light) |

Device & Sensors

| Hook | Description | |------|-------------| | useDeviceOrientation | Read gyroscope orientation (alpha/beta/gamma) | | useGeolocation | Watch GPS coordinates | | useNetworkStatus | Track online/offline and connection type | | useBattery | Read battery level and charging state | | useVibrate | Trigger device vibration | | useWakeLock | Prevent screen from sleeping | | useGamepad | Detect connected gamepads | | useSpeechRecognition | Convert speech to text via Web Speech API | | useSpeechSynthesis | Convert text to speech | | useEyeDropper | Pick a color from the screen |

Timing

| Hook | Description | |------|-------------| | useDebounce | Debounce a value by a given delay | | useThrottle | Throttle a value to update at most once per interval | | useInterval | Run a callback on a repeating interval | | useTimeout | Run a callback after a delay, with reset/clear | | useAnimationFrame | Run a callback on every animation frame | | useCountdown | Countdown timer with start, stop, and reset |


Reference

useOutsideClick

Fires a callback when the user clicks or touches outside a given element.

const ref = useRef<HTMLDivElement>(null)
useOutsideClick(ref, () => setOpen(false))
useOutsideClick(ref, handler, enabled?)

| Param | Type | Default | |-------|------|---------| | ref | RefObject<HTMLElement> | — | | handler | (event: MouseEvent \| TouchEvent) => void | — | | enabled | boolean | true |


useHover

Returns true while the mouse is over the element. Supports enter/leave delays to prevent flickering.

const ref = useRef<HTMLDivElement>(null)
const isHovered = useHover(ref, { enterDelay: 200 })
useHover(ref, options?) → boolean

| Option | Type | Default | |--------|------|---------| | enterDelay | number (ms) | 0 | | leaveDelay | number (ms) | 0 |


useDrag

Tracks mouse drag on an element, returning whether a drag is active, the total delta, and the current pointer position.

const ref = useRef<HTMLDivElement>(null)
const { isDragging, delta, position } = useDrag(ref)
useDrag(ref) → { isDragging: boolean, delta: {x,y}, position: {x,y} }

usePointerPosition

Tracks the mouse pointer position across the entire page.

const { x, y, clientX, clientY } = usePointerPosition()
usePointerPosition() → { x, y, clientX, clientY }

x/y are page-relative (include scroll); clientX/clientY are viewport-relative.


useContextMenu

Intercepts right-click on an element, prevents the default browser menu, and calls the handler.

const ref = useRef<HTMLDivElement>(null)
useContextMenu(ref, (e) => openMenu(e.clientX, e.clientY))
useContextMenu(ref, callback)

useMouseLeaveWindow

Fires when the mouse pointer exits the browser window entirely. Useful for exit-intent popups.

useMouseLeaveWindow(() => setShowExitPopup(true))
useMouseLeaveWindow(callback)

useSwipe

Detects swipe direction from touch events on an element.

const ref = useRef<HTMLDivElement>(null)
useSwipe(ref, {
  onSwipe: (direction) => console.log(direction) // "left" | "right" | "up" | "down"
})
useSwipe(ref, { onSwipe, threshold? })

| Option | Type | Default | |--------|------|---------| | onSwipe | (dir: "left"\|"right"\|"up"\|"down") => void | — | | threshold | number (px) | 50 |


useDoubleTap

Fires a callback when the user double-taps an element (touch only).

const ref = useRef<HTMLDivElement>(null)
useDoubleTap(ref, () => like())
useDoubleTap(ref, callback, options?)

| Option | Type | Default | |--------|------|---------| | threshold | number (ms between taps) | 300 |


usePinch

Tracks a two-finger pinch gesture, reporting scale and midpoint origin.

const ref = useRef<HTMLDivElement>(null)
usePinch(ref, {
  onPinch: ({ scale, origin }) => setScale(scale),
  onPinchEnd: ({ scale }) => commitScale(scale),
})
usePinch(ref, { onPinch, onPinchEnd? })

scale is relative to the start of the gesture (1.0 = no change).


useLongPress

Fires a callback after the user holds down on an element for the specified duration.

const ref = useRef<HTMLButtonElement>(null)
useLongPress(ref, () => deleteItem(), {
  threshold: 800,
  onStart: () => showProgressBar(),
  onCancel: () => hideProgressBar(),
})
useLongPress(ref, callback, options?)

| Option | Type | Default | |--------|------|---------| | threshold | number (ms) | 500 | | onStart | () => void | — | | onCancel | () => void | — |

onCancel is not called after a successful long press.


useKeyPress

Returns true while a specific key is held down.

const isShiftHeld = useKeyPress("Shift")
const isEscape = useKeyPress("Escape")
useKeyPress(key: string) → boolean

Key names follow the KeyboardEvent.key spec.


useKeyCombo

Listens for a keyboard shortcut and fires a callback.

useKeyCombo("ctrl+k", (e) => { e.preventDefault(); openSearch() })
useKeyCombo("ctrl+shift+p", openCommandPalette)
useKeyCombo("escape", closeModal)
useKeyCombo(combo, callback, options?)

Combo syntax: "k" · "escape" · "ctrl+k" · "ctrl+shift+k" · "meta+k"

| Option | Type | Default | |--------|------|---------| | enabled | boolean | true | | target | HTMLElement \| Document | document |


useArrowNavigation

Handles arrow key navigation within a container, moving focus between focusable children.

const ref = useRef<HTMLUListElement>(null)
useArrowNavigation(ref, { orientation: "vertical", loop: true })

return (
  <ul ref={ref}>
    <li tabIndex={0}>Item 1</li>
    <li tabIndex={0}>Item 2</li>
  </ul>
)
useArrowNavigation(ref, options?)

| Option | Type | Default | |--------|------|---------| | selector | string | "[role='option'], [role='menuitem'], li, button" | | orientation | "vertical" \| "horizontal" \| "both" | "vertical" | | loop | boolean | true |


useFocusTrap

Traps Tab/Shift+Tab focus inside an element and focuses the first focusable element on activation. Essential for accessible modals and dialogs.

const ref = useRef<HTMLDivElement>(null)
useFocusTrap(ref, isModalOpen)

return <div ref={ref}>
  <input />
  <button>Close</button>
</div>
useFocusTrap(ref, enabled?)

useFocusWithin

Returns true when focus is inside an element or any of its descendants.

const ref = useRef<HTMLFormElement>(null)
const active = useFocusWithin(ref)
useFocusWithin(ref) → boolean

useIntersectionObserver

Observes when an element enters or leaves the viewport. Useful for lazy loading and infinite scroll.

const ref = useRef<HTMLDivElement>(null)
const { isIntersecting, ratio } = useIntersectionObserver(ref, {
  threshold: 0.5,
})
useIntersectionObserver(ref, options?) → { isIntersecting: boolean, ratio: number }

| Option | Type | Default | |--------|------|---------| | threshold | number \| number[] | 0 | | rootMargin | string | "0px" | | root | Element \| null | null |


useIdle

Returns true after the user has had no mouse/keyboard/touch/scroll activity for the given duration.

const isIdle = useIdle(30_000) // 30 seconds
if (isIdle) lockScreen()
useIdle(timeout: number) → boolean

usePageVisibility

Returns true when the current browser tab is visible, false when hidden (minimised or switched away).

const isVisible = usePageVisibility()
useEffect(() => { if (!isVisible) pauseVideo() }, [isVisible])
usePageVisibility() → boolean

useScrollPosition

Tracks the current scroll position of the window.

const { x, y } = useScrollPosition()
useScrollPosition() → { x: number, y: number }

useScrollDirection

Returns the current scroll direction or null before the first scroll.

const direction = useScrollDirection() // "up" | "down" | null
useScrollDirection() → "up" | "down" | null

useScrollProgress

Returns scroll progress as a value from 0 to 1. Pass a ref to track an element's internal scroll, or omit it to track the page.

const progress = useScrollProgress()  // page scroll
const progress = useScrollProgress(ref) // element scroll
useScrollProgress(ref?) → number

useScrollLock

Locks body scroll when locked is true. Uses position: fixed to prevent bounce on iOS Safari and restores the scroll position on unlock.

useScrollLock(isModalOpen)
useScrollLock(locked: boolean): void

useResizeObserver

Tracks the content dimensions of an element using ResizeObserver.

const ref = useRef<HTMLDivElement>(null)
const { width, height } = useResizeObserver(ref)
useResizeObserver(ref) → { width: number, height: number }

useWindowSize

Tracks the browser window's inner dimensions.

const { width, height } = useWindowSize()
useWindowSize() → { width: number, height: number }

useCopyToClipboard

Copies text to the clipboard and provides a copied flag that resets after a delay.

const { copied, copy } = useCopyToClipboard()

return (
  <button onClick={() => copy("Hello!")}>
    {copied ? "Copied!" : "Copy"}
  </button>
)
useCopyToClipboard(resetDelay?) → { copied: boolean, copy: (text: string) => Promise<void> }

| Param | Type | Default | |-------|------|---------| | resetDelay | number (ms) | 2000 |


useMediaQuery

Subscribes to a CSS media query and returns whether it currently matches.

const isDark = useMediaQuery("(prefers-color-scheme: dark)")
const isMobile = useMediaQuery("(max-width: 768px)")
useMediaQuery(query: string) → boolean

useTextSelection

Tracks the user's current text selection anywhere on the page.

const { text, rect } = useTextSelection()
// rect is the DOMRect of the selection — useful for positioning a tooltip
useTextSelection() → { text: string, rect: DOMRect | null }

useDropZone

Makes an element a file drop target. Returns isOver to indicate an active drag-over state.

const ref = useRef<HTMLDivElement>(null)
const { isOver } = useDropZone(ref, {
  onDrop: (files) => uploadFiles(files),
})
useDropZone(ref, { onDrop, onDragOver?, onDragLeave? }) → { isOver: boolean }

useDeviceOrientation

Reads the device orientation from the gyroscope. Returns null values on desktop.

const { alpha, beta, gamma } = useDeviceOrientation()
useDeviceOrientation() → { alpha: number | null, beta: number | null, gamma: number | null }

useGeolocation

Watches GPS coordinates via the Geolocation API.

const { loading, position, error } = useGeolocation({ enableHighAccuracy: true })
const lat = position?.coords.latitude
useGeolocation(options?) → { loading, position: GeolocationPosition | null, error: GeolocationPositionError | null }

| Option | Type | Default | |--------|------|---------| | enableHighAccuracy | boolean | false | | timeout | number (ms) | Infinity | | maximumAge | number (ms) | 0 |


useNetworkStatus

Tracks online/offline status and connection type via the Network Information API.

const { online, effectiveType } = useNetworkStatus()
// effectiveType: "slow-2g" | "2g" | "3g" | "4g" | null
useNetworkStatus() → { online: boolean, type: string | null, effectiveType: string | null }

useBattery

Reads battery level and charging state via the Battery Status API (Chrome only).

const { supported, loading, level, charging, dischargingTime } = useBattery()
useBattery() → { supported, loading, level: number | null, charging: boolean | null, chargingTime: number | null, dischargingTime: number | null }

useVibrate

Returns a function that triggers device vibration. No-ops silently on unsupported devices.

const vibrate = useVibrate()

vibrate(200)                       // 200ms buzz
vibrate([100, 50, 100])            // pattern: buzz, pause, buzz
vibrate(0)                         // stop
useVibrate() → (pattern: number | number[]) => void

useDoubleClick

Fires a callback when the user double-clicks an element (mouse only — for touch use useDoubleTap).

const ref = useRef<HTMLDivElement>(null)
useDoubleClick(ref, () => likePost())
useDoubleClick(ref, callback, options?)

| Option | Type | Default | |--------|------|---------| | threshold | number (ms between clicks) | 300 |


usePointerLock

Locks the pointer to an element, hiding the cursor and providing raw mouse delta. Used in games and 3D editors.

const ref = useRef<HTMLDivElement>(null)
const { isLocked, lock, unlock } = usePointerLock(ref)
usePointerLock(ref) → { isLocked: boolean, lock: () => Promise<void>, unlock: () => void }

useKeySequence

Fires a callback when the user presses a specific sequence of keys in order.

useKeySequence(
  ["ArrowUp", "ArrowUp", "ArrowDown", "ArrowDown"],
  () => activateCheats()
)
useKeySequence(sequence: string[], callback: () => void)

useFocusReturn

Captures the currently focused element on mount and restores focus to it when the component unmounts. Essential for accessible modals.

function Modal() {
  useFocusReturn()
  return <dialog>...</dialog>
}
useFocusReturn() → void

useTabFocus

Returns true when the user is navigating via the Tab key, false when using mouse/touch. Useful for showing focus rings only for keyboard users.

const isTabFocused = useTabFocus()
useTabFocus() → boolean

useScrollIntoView

Returns a function that smoothly scrolls a ref element into view.

const ref = useRef<HTMLDivElement>(null)
const scrollTo = useScrollIntoView(ref, { behavior: "smooth", block: "center" })

<button onClick={scrollTo}>Go to section</button>
useScrollIntoView(ref, options?) → () => void

useScrollSpy

Watches multiple section refs and returns the index of the one currently most visible in the viewport.

const active = useScrollSpy([ref1, ref2, ref3], { threshold: 0.5 })
useScrollSpy(refs, options?) → number

| Option | Type | Default | |--------|------|---------| | threshold | number | 0.5 | | rootMargin | string | "0px" |


useInfiniteScroll

Fires an async callback when a sentinel element enters the viewport. Use a ref on a bottom marker to load more items.

const { loading } = useInfiniteScroll(sentinelRef, async () => {
  const more = await fetchNextPage()
  setItems(prev => [...prev, ...more])
})
useInfiniteScroll(ref, onLoadMore, options?) → { loading: boolean }

useElementSize

Tracks the rendered width and height of an element using ResizeObserver + getBoundingClientRect.

const ref = useRef<HTMLDivElement>(null)
const { width, height } = useElementSize(ref)
useElementSize(ref) → { width: number, height: number }

useElementPosition

Tracks the viewport-relative bounding rect of an element, updating on scroll and resize.

const ref = useRef<HTMLDivElement>(null)
const { top, left, width, height } = useElementPosition(ref)
useElementPosition(ref) → { x, y, top, left, right, bottom, width, height }

useContainerQuery

Matches an element's width against named breakpoints using ResizeObserver.

const ref = useRef<HTMLDivElement>(null)
const matches = useContainerQuery(ref, { sm: 300, md: 600, lg: 900 })
// matches.md === true when element width >= 600px
useContainerQuery(ref, breakpoints: Record<string, number>) → Record<string, boolean>

useMutationObserver

Observes DOM mutations on an element and fires a callback on each change.

const ref = useRef<HTMLDivElement>(null)
useMutationObserver(ref, (mutations) => console.log(mutations))
useMutationObserver(ref, callback, options?)

Default options: { childList: true, subtree: true }


useFullscreen

Controls the Fullscreen API for a given element.

const ref = useRef<HTMLDivElement>(null)
const { isFullscreen, enter, exit, toggle } = useFullscreen(ref)
useFullscreen(ref) → { isFullscreen: boolean, enter: () => Promise<void>, exit: () => Promise<void>, toggle: () => Promise<void> }

usePaste

Listens for paste events anywhere on the page and calls the callback with the pasted text.

usePaste((text, event) => {
  console.log("Pasted:", text)
})
usePaste(callback: (text: string, event: ClipboardEvent) => void)

useShare

Wraps the Web Share API. Falls back gracefully on unsupported browsers.

const { supported, share } = useShare()
await share({ title: "Hello", url: window.location.href })
useShare() → { supported: boolean, share: (data: ShareData) => Promise<void> }

usePermission

Queries the Permissions API for the current state of a browser permission.

const state = usePermission("camera") // "granted" | "denied" | "prompt" | null
usePermission(name: PermissionName) → "granted" | "denied" | "prompt" | null

useNotification

Requests notification permission and provides a notify helper.

const { permission, requestPermission, notify } = useNotification()
await requestPermission()
notify("Hello!", { body: "World" })
useNotification() → { supported, permission, requestPermission, notify }

useReducedMotion

Returns true when the user has requested reduced motion via OS/browser settings.

const reduced = useReducedMotion()
const duration = reduced ? 0 : 300
useReducedMotion() → boolean

useColorScheme

Returns the user's preferred color scheme.

const scheme = useColorScheme() // "dark" | "light"
useColorScheme() → "dark" | "light"

useWakeLock

Prevents the screen from sleeping using the Screen Wake Lock API (Chrome/Edge only).

const { supported, active, request, release } = useWakeLock()
useWakeLock() → { supported: boolean, active: boolean, request: () => Promise<void>, release: () => Promise<void> }

useGamepad

Detects connected gamepads via the Gamepad API.

const { gamepads, connected } = useGamepad()
useGamepad() → { gamepads: Gamepad[], connected: boolean }

useSpeechRecognition

Converts speech to text using the Web Speech API (Chrome/Edge only).

const { supported, listening, transcript, start, stop, reset } = useSpeechRecognition()
useSpeechRecognition(lang?) → { supported, listening, transcript, start, stop, reset }

| Param | Type | Default | |-------|------|---------| | lang | string | "en-US" |


useSpeechSynthesis

Converts text to speech using the SpeechSynthesis API.

const { speak, speaking, voices, cancel } = useSpeechSynthesis()
speak("Hello world", { rate: 1.2, pitch: 1 })
useSpeechSynthesis() → { supported, speaking, voices, speak, cancel }

useEyeDropper

Opens the browser's color picker to sample a color from anywhere on the screen (Chrome 95+).

const { supported, color, open } = useEyeDropper()
const hex = await open() // e.g. "#ff5733"
useEyeDropper() → { supported: boolean, color: string | null, open: () => Promise<string | null> }

useDebounce

Returns a debounced copy of a value that only updates after the specified delay.

const [search, setSearch] = useState("")
const debounced = useDebounce(search, 400)

useEffect(() => {
  fetchResults(debounced)
}, [debounced])
useDebounce<T>(value: T, delay: number) → T

useThrottle

Returns a throttled copy of a value that updates at most once per interval.

const [pos, setPos] = useState({ x: 0, y: 0 })
const throttled = useThrottle(pos, 100)
useThrottle<T>(value: T, delay: number) → T

useInterval

Runs a callback on a repeating interval. Pass null as the delay to pause.

useInterval(() => setCount(c => c + 1), running ? 1000 : null)
useInterval(callback: () => void, delay: number | null)

useTimeout

Fires a callback after a delay. Returns reset and clear controls.

const { reset, clear } = useTimeout(() => setFired(true), 3000)
useTimeout(callback, delay) → { reset: () => void, clear: () => void }

useAnimationFrame

Calls a callback on every animation frame, passing the delta time since the last frame.

useAnimationFrame((deltaTime) => {
  setAngle(a => (a + deltaTime * 0.1) % 360)
})
useAnimationFrame(callback: (deltaTime: number) => void)

useCountdown

A countdown timer with start, stop, and reset controls.

const { count, running, start, stop, reset } = useCountdown(60)
useCountdown(initialSeconds: number) → { count, running, start, stop, reset }

Requirements

  • React 16.8+

License

MIT