@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 install
Autolinked 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();
Haptics.light();
Haptics.success();
Haptics.trigger(HAPTIC.ERROR);
The nine
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);
Haptics.pattern([0, 40, 80, 40]);
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);
Wire 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