@amrshbib/react-native-haptic-feedback
v0.0.1
Published
Semantic haptics for React Native. Real Taptic Engine on iOS (selection, impact, notification) and Core Haptics for arbitrary durations; VibrationEffect primitives on Android. One vocabulary, no dead 400ms buzz.
Maintainers
Readme
@amrshbib/react-native-haptic-feedback
Semantic haptics for React Native. Nine named events — the ones Apple designed — routed to the
real Taptic Engine on iOS and to VibrationEffect primitives on Android, plus Core Haptics for
anything with a duration.
React Native ships Vibration, and on iOS it plays exactly one thing: the 400 ms slab an
incoming call uses. It ignores durations, ignores patterns, and is far too heavy to put behind
a button. That is why most apps end up with Platform.OS === "android" && Vibration.vibrate(20)
and no haptics at all on the platform that has the best ones. This is the fix.
Install
npm install @amrshbib/react-native-haptic-feedback
cd ios && pod installAutolinked on both platforms. Android's VIBRATE permission is merged in from the library
manifest — there is nothing to add to the app. Rebuild after installing; restarting Metro is not
enough.
Use
import Haptics, { HAPTIC } from "@amrshbib/react-native-haptic-feedback";
Haptics.selection(); // a value changed under the finger
Haptics.light(); // a tap that did something
Haptics.success(); // it worked
Haptics.trigger(HAPTIC.ERROR);The nine
| Name | Means | iOS | Android (API 30+) |
| --- | --- | --- | --- |
| selection | a value changed under the finger — a tab, a chip, a stepper | UISelectionFeedbackGenerator | PRIMITIVE_TICK @ 0.45 |
| impactLight | something small landed | impact, light | PRIMITIVE_CLICK @ 0.5 |
| impactMedium | something landed | impact, medium | PRIMITIVE_CLICK @ 0.75 |
| impactHeavy | something heavy landed | impact, heavy | PRIMITIVE_CLICK @ 1.0 |
| impactSoft | a dull edge | impact, soft | PRIMITIVE_TICK @ 1.0 |
| impactRigid | a hard edge | impact, rigid | PRIMITIVE_CLICK @ 1.0 |
| notificationSuccess | it worked | notification, success | two clicks, 90 ms apart |
| notificationWarning | read this | notification, warning | click then tick |
| notificationError | it did not work | notification, error | three clicks |
Android falls back a rung at a time — API 30 primitives, then API 29 predefined effects, then an
API 26 waveform with amplitudes, then a bare duration. A caller asking for selection on a 2017
handset still gets the shortest thing that phone can do.
Durations
Haptics.vibrate(30); // 30 ms, honoured on both platforms
Haptics.pattern([0, 40, 80, 40]); // wait, buzz, wait, buzz
Haptics.cancel();pattern takes the same shape as React Native's own Vibration.vibrate on Android, so patterns
written for it port across — and now run on iOS too, through Core Haptics.
Waiting
const stop = Haptics.pulse({ interval: 900 });
// …
stop();A tick every interval until you stop it — for the thing a spinner cannot say, when the phone is
face-down or the eyes are elsewhere. It stops itself after limit ticks (default 30) so a request
that never returns cannot leave a phone buzzing in a pocket.
The switch
Haptics.setEnabled(false); // one gate, held in JS, no bridge crossing to say noWire it to wherever the app keeps the user's answer. Held in JS on purpose: a gate that had to cross the bridge to decline would cost a round trip per tap to do nothing.
Handlers
<Button onPress={Haptics.haptic(handleSave, HAPTIC.SUCCESS)} />The tick fires before the handler — the touch is what is being answered, not the result of it.
What the device can do
const { supported, engine, systemEnabled } = await Haptics.getCapabilities();engine is "core-haptics", "vibrator", or "none" — the last meaning no motor at all, which
is most simulators and every iPad. systemEnabled reflects Android's touch-feedback setting;
iOS has no public read for System Haptics and always reports true, so do not branch on it — the
platform enforces its own setting below this library either way.
Also
prepare()— both platforms power the motor down between uses, and the first tick after idle lands late enough to feel detached from the touch. Call it when a screen that will tick opens; not per tap, which is the same latency moved.- Repeats are collapsed. Two ticks of the same kind inside 24 ms are one tick — well under a deliberate double-tap, and enough to keep a fast-scrolling list from returning a smear.
- A missing native module never throws. A build that has not been recompiled, a simulator, a web target: every call goes quiet, with one dev-mode warning. Haptics are decoration, and decoration must not be able to take a screen down.
- Android honours the system touch-feedback setting for the nine named events, and not for
vibrate/pattern— turning touch feedback off is a statement about taps, not about the buzz that says a voice recording has started.
Requirements
React Native ≥ 0.71 · iOS 13 · Android API 24
License
MIT © Amr Shbib
