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

react-native-core-haptics

v0.1.1

Published

iOS Core Haptics for React Native. Low-latency, non-coalescing haptic feedback for continuous input (scrubbing, sliders), via Nitro Modules. Worklet-callable.

Readme

react-native-core-haptics

Low-latency, non-coalescing haptic feedback for continuous input in React Native. Backed by iOS Core Haptics + Nitro Modules, so every scrub crossing fires a distinct tap (even under 60 Hz oscillation), and you can call it straight from a Reanimated worklet.

Version License iOS build Android build

The problem

iOS gives you two haptic APIs. UIKit's feedback generators (UIImpactFeedbackGenerator, UISelectionFeedbackGenerator, UINotificationFeedbackGenerator) coalesce above ~25 Hz, so rapid threshold crossings on a chart scrubber or a slider step feel flat. Core Haptics (CHHapticEngine) doesn't coalesce; every transient fires.

But for high-rate continuous input, just reaching for Core Haptics is not enough. Two things matter on the gesture hot path:

  1. The pattern player is cached, not rebuilt per call. Building a fresh CHHapticPattern and CHHapticPatternPlayer every tick is ~1ms of allocation on the gesture thread. At 60 Hz that's noticeable work running on the path that decides when the tick fires.
  2. The call is worklet-callable. If a tick has to hop back to the JS thread before it touches native, you've added a bridge round-trip per gesture event.

The solution

react-native-core-haptics is a small Nitro Module focused on the scrub use case:

  • Per-style CHHapticPatternPlayers are built once (lazily on first use, or eagerly via prepare()) and reused. The hot path allocates nothing.
  • The hybrid object is JSI-resident, so tick() is callable directly from a Reanimated worklet without runOnJS and without a bridge hop.

Android dispatches via View.performHapticFeedback with SEGMENT_TICK on Android 14+ (the scrubber-specific constant the platform added in API 34) and CLOCK_TICK on older versions. No permissions required.

Install

npm install react-native-core-haptics react-native-nitro-modules

iOS:

cd ios && pod install

Requirements:

  • React Native New Architecture enabled (newArchEnabled: true in app.json, or RCT_NEW_ARCH_ENABLED=1)
  • iOS 13+ (Core Haptics minimum)
  • Android API 21+ (best feel on API 34+ where SEGMENT_TICK is available)
  • Reanimated 3+ and Gesture Handler if you want to call from worklets. The babel plugin name differs by major: Reanimated 3 uses react-native-reanimated/plugin, Reanimated 4 uses react-native-worklets/plugin. Make sure your babel.config.js matches the version you installed.

Usage

For a working scrub demo with style switcher and a 200-step rate test, see example/.

import { Gesture, GestureDetector } from 'react-native-gesture-handler'
import { useSharedValue } from 'react-native-reanimated'
import { CoreHaptics } from 'react-native-core-haptics'

const lastIndex = useSharedValue(0)

const pan = Gesture.Pan()
  .onBegin(() => {
    'worklet'
    CoreHaptics.prepare()
  })
  .onUpdate(e => {
    'worklet'
    const idx = Math.round(e.x / SEGMENT_WIDTH)
    if (idx !== lastIndex.value) {
      lastIndex.value = idx
      CoreHaptics.tick() // worklet-direct, no runOnJS
    }
  })
  .onEnd(() => {
    'worklet'
    CoreHaptics.stop()
  })

Non-worklet usage works too: CoreHaptics.tick() is safe to call from the JS thread.

API

| Method | Purpose | |---|---| | prepare() | Warm up the engine + cache the default pattern player. Cheap, idempotent. Call on touch-start so the first tick() has zero cold-start latency. Optional. | | tick() | Fire a single transient using the selection preset. Non-coalescing: each call produces a distinct buzz. Safe from worklets. | | tickStyled(style) | Fire a transient using a named preset (see below). Each style gets its own cached player, still zero-alloc on the hot path. | | tickCustom({ intensity?, sharpness? }) | Raw CHHapticEventParameter control. iOS full; Android maps intensity → closest preset (≥0.7strong, 0.35–0.7selection, <0.35soft; sharpness is unused). Each call builds a fresh player (~1ms), so prefer tickStyled for 60 Hz hot paths. | | stop() | Call on touch-end. Keeps the engine warm for rapid re-touch. | | teardown() | Hard-shutdown the engine. Rarely needed; the engine auto-recovers from system interruptions. |

Named styles

| Style | iOS (intensity / sharpness) | Android HapticFeedbackConstant | |---|:---:|---| | selection (default) | 0.45 / 0.6 (matches UISelectionFeedbackGenerator) | SEGMENT_TICK (API 34+) · CLOCK_TICK (older) | | soft | 0.3 / 0.3 (duller, lower-intensity) | KEYBOARD_TAP | | strong | 0.9 / 0.9 (sharper, heavier) | LONG_PRESS |

How it compares

| | worklet-callable | iOS backend | Android backend | min iOS | |---|:-:|---|---|:-:| | expo-haptics | ❌ | UIKit feedback generators (coalesce >25 Hz) | HapticFeedbackConstants | 13 | | react-native-haptic-feedback | ❌ | CHHapticEngine (player rebuilt per call) with UIKit fallback | Vibrator / VibrationEffect | 10 | | react-native-nitro-haptics | ✅ | UISelectionFeedbackGenerator (coalesces >25 Hz) | HapticFeedbackConstants | 13 | | react-native-core-haptics | | CHHapticEngine (cached per-style players, zero-alloc hot path) | HapticFeedbackConstants.SEGMENT_TICK (API 34+) | 13 |

When to use this vs expo-haptics

Use expo-haptics for discrete event haptics: button taps, notifications, success/error feedback. The coalescing behavior is a feature there, not a bug.

Use react-native-core-haptics for continuous-input haptics: scrub bars, sliders, lists with snap-to, gesture boundaries. Anywhere the user crosses a threshold and you want them to feel the crossing, not a coalesced average.

The two can coexist in the same app.

FAQ

Why not an Expo module? Expo modules can't do JSI worklet dispatch as cleanly as Nitro can. Since worklet-callable tick() is half the reason this package exists, Nitro is the right primitive. Expo apps still install this via normal autolinking; you don't need to eject or use a config plugin.

Does it replace expo-haptics? No. Different use cases (see above).

What about iPads / older iPhones? Core Haptics isn't available on most iPads or on iPhones before the 8. When CHHapticEngine.capabilitiesForHardware().supportsHaptics is false, the iOS path falls back to UISelectionFeedbackGenerator. You'll still get some haptics, just with the coalescing behavior of that API.

What about react-native-haptic-feedback's ignoreAndroidSystemSettings? Not supported; this package respects the user's system haptic setting.

Can I tune the feel? Yes. Either pick a named preset with tickStyled('soft' | 'strong') or pass raw parameters with tickCustom({ intensity, sharpness }) (iOS full; Android picks the closest preset). The tick() hot path keeps the defaults (0.45 / 0.6, matching UISelectionFeedbackGenerator).

Credits

Bootstrapped with create-nitro-module. Engine lifecycle pattern informed by the Core Haptics cookbook and the Capturing the fine details of a gesture WWDC sample.

License

MIT. See LICENSE.