Skip to content

mkuczera/react-native-haptic-feedback

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

331 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

react-native-haptic-feedback

The most complete haptic feedback library for React Native — Core Haptics on iOS, rich Composition API on Android, custom patterns, and a developer-friendly hook.

GitHub Sponsors npm npm downloads

Full documentation →

If this library saves you time, consider sponsoring its development. ⭐


Try it on your device

Want to feel the difference between every haptic type — or test your own custom pattern notation — before writing a single line of code?

Haptic Feedback Tryout is a companion app that lets you tap through all available feedback types and compose your own patterns interactively using the o O . - = notation.

Download on the App Store — iOS available now, Android coming soon.


Contributions Welcome

Contributors

Made with contrib.rocks.


Requirements

Platform Minimum version
iOS 13.0 (Core Haptics)
Android API 23 (Android 6.0)
React Native 0.71.0
Web Browsers with Vibration API

Installation

npm install react-native-haptic-feedback
# or
yarn add react-native-haptic-feedback

React Native 0.71+ uses auto-linking — no extra steps needed.


Basic Usage

import RNHapticFeedback from "react-native-haptic-feedback";

RNHapticFeedback.trigger("impactMedium");

// With options
RNHapticFeedback.trigger("notificationSuccess", {
  enableVibrateFallback: true, // iOS: vibrate if Core Haptics unavailable
  ignoreAndroidSystemSettings: false,
});

Named exports are also available:

import { trigger } from "react-native-haptic-feedback";
trigger("impactLight");

API Reference

trigger(type, options?)

Play a predefined haptic type.

RNHapticFeedback.trigger(type: HapticFeedbackTypes | string, options?: HapticOptions): void
Option Default Description
enableVibrateFallback false iOS: play AudioServicesPlaySystemSound as a last resort on devices with no Taptic Engine (e.g. iPod touch). Has no effect on devices that have a Taptic Engine — those use UIKit generators automatically.
ignoreAndroidSystemSettings false Android: trigger even if vibration is disabled in system settings

impact(type?, intensity?, options?)

Play a haptic with a custom intensity (0.0–1.0). On iOS the intensity is applied precisely via CHHapticEngine; on Android it maps to VibrationEffect amplitude.

RNHapticFeedback.impact(
  type?: HapticFeedbackTypes,   // default: 'impactMedium'
  intensity?: number,            // 0.0–1.0, default: 0.7
  options?: HapticOptions
): void
import { impact } from "react-native-haptic-feedback";

impact("impactHeavy", 0.3); // gentle heavy tap
impact("rigid", 1.0); // full-force crisp tap

stop()

Cancel the current haptic player and stop the engine.

RNHapticFeedback.stop(): void

isSupported()

Synchronously returns true if Core Haptics is supported on the device (iOS 13+ hardware). Always returns true on Android if the device has a vibrator.

RNHapticFeedback.isSupported(): boolean

triggerPattern(events, options?)

Play a custom sequence of haptic events.

RNHapticFeedback.triggerPattern(events: HapticEvent[], options?: HapticOptions): void
interface HapticEvent {
  time: number; // ms from pattern start
  type?: "transient" | "continuous";
  duration?: number; // ms — for continuous events only
  intensity?: number; // 0.0–1.0
  sharpness?: number; // 0.0–1.0
}

playAHAP(fileName)

Play an Apple Haptic and Audio Pattern (.ahap) file. iOS only — resolves immediately on Android.

RNHapticFeedback.playAHAP(fileName: string): Promise<void>

Pass the file name (e.g. "heartbeat.ahap") without any path prefix. The native code searches for the file in two locations, in order:

  1. A haptics/ subdirectory inside the app bundle
  2. The bundle root

For cross-platform usage, prefer playHaptic below.

Setting up AHAP files in Xcode

AHAP files must be added to the iOS app bundle — they are not bundled by Metro or CocoaPods. Follow these steps:

  1. Create a haptics/ folder inside your Xcode project directory (e.g. ios/YourApp/haptics/).

  2. Place your .ahap files in that folder:

ios/YourApp/haptics/
├── heartbeat.ahap
├── rumble.ahap
└── celebration.ahap
  1. Add the files to Xcode:

    • Open your .xcworkspace in Xcode
    • Right-click your app target in the project navigator → Add Files to "YourApp"
    • Select the .ahap files (or the entire haptics/ folder)
    • Make sure "Copy items if needed" is unchecked (files are already in the project directory)
    • Make sure your app target is checked under "Add to targets"
  2. Verify they appear in Build Phases:

    • Select your app target → Build PhasesCopy Bundle Resources
    • All .ahap files should be listed there. If not, click + and add them.

Once added, the files are bundled into the app at build time and playAHAP("heartbeat.ahap") will find them.

AHAP file format

An .ahap file is a JSON document describing haptic events. Here's a minimal example:

{
  "Version": 1.0,
  "Pattern": [
    {
      "Event": {
        "EventType": "HapticTransient",
        "Time": 0.0,
        "EventParameters": [
          { "ParameterID": "HapticIntensity", "ParameterValue": 0.5 },
          { "ParameterID": "HapticSharpness", "ParameterValue": 0.3 }
        ]
      }
    },
    {
      "Event": {
        "EventType": "HapticTransient",
        "Time": 0.15,
        "EventParameters": [
          { "ParameterID": "HapticIntensity", "ParameterValue": 1.0 },
          { "ParameterID": "HapticSharpness", "ParameterValue": 0.5 }
        ]
      }
    }
  ]
}

See Apple's Representing Haptic Patterns in AHAP Files for the full specification. You can also design patterns visually using the Haptic Sampler section in Xcode's Core Haptics tools.

playHaptic(ahapFile, fallback, options?)

Cross-platform wrapper for AHAP playback. Plays the .ahap file on iOS and falls back to a triggerPattern call on Android.

import { playHaptic, pattern } from "react-native-haptic-feedback";

// iOS: plays my-effect.ahap via Core Haptics
// Android: plays the fallback pattern via Vibrator API
await playHaptic("my-effect.ahap", pattern("oO.O"));

This is the recommended approach for cross-platform apps — design your haptic in an .ahap file for the best iOS experience, and provide a pattern() fallback for Android.

getSystemHapticStatus()

Returns the device's haptic availability and — on Android — current ringer mode.

RNHapticFeedback.getSystemHapticStatus(): Promise<SystemHapticStatus>

interface SystemHapticStatus {
  vibrationEnabled: boolean;
  /** Android: 'silent' | 'vibrate' | 'normal'. iOS: null (not exposed by the OS). */
  ringerMode: 'silent' | 'vibrate' | 'normal' | null;
}

Use the isRingerSilent helper to check for silent mode:

import {
  getSystemHapticStatus,
  isRingerSilent,
} from "react-native-haptic-feedback";

const status = await getSystemHapticStatus();
if (isRingerSilent(status)) {
  // Android: ringer is silent — show a visual indicator instead
}
// iOS: ringerMode is always null (not exposed by the OS), so isRingerSilent returns false

setEnabled(value) / isEnabled()

Library-wide kill switch. When disabled, all trigger, triggerPattern, playAHAP, playHaptic, and stop calls become no-ops.

import RNHapticFeedback from "react-native-haptic-feedback";

// Respect user's in-app haptics preference
RNHapticFeedback.setEnabled(userPreference.hapticsEnabled);

// Check current state
if (RNHapticFeedback.isEnabled()) {
  /* ... */
}

The setting is in-memory only — persist it yourself (e.g. AsyncStorage) if it should survive app restarts.


Pattern Notation Helper

Build HapticEvent[] from a compact string notation:

Character Meaning Time advance Total O_O spacing
o Soft transient (intensity 0.4, sharpness 0.4) 100 ms
O Strong transient (intensity 1.0, sharpness 0.8) 100 ms
. Short gap +150 ms 250 ms
- Medium gap +400 ms 500 ms
= Long gap +1000 ms 1100 ms

Consecutive haptic events (OO) are spaced 100 ms apart — the minimum interval the Taptic Engine (iOS) and vibrator motors (Android) can render as distinct pulses. Gap characters add progressively more space on top: . for a short beat (250 ms), - for a half-second pause, = for a full second rest.

import { pattern, PATTERN_CHARS } from "react-native-haptic-feedback";
import type { PatternChar } from "react-native-haptic-feedback";

RNHapticFeedback.triggerPattern(pattern("oO.O"));
// → soft, strong, 100ms pause, strong

pattern() throws a TypeError at runtime if the string contains any character not in PATTERN_CHARS.

Compile-time validation: When you pass a string literal, TypeScript catches invalid characters before runtime:

import type { AssertValidPattern } from "react-native-haptic-feedback";

pattern("oO.O"); // ✅ compiles
pattern("oXO"); // ✗ TypeScript error — 'X' is not a valid PatternChar

This works automatically — no extra setup needed. If the argument is a runtime string variable (not a literal), validation happens at runtime via TypeError instead.

Programmatic validation — use PATTERN_CHARS to check user input before calling pattern():

import { PATTERN_CHARS } from "react-native-haptic-feedback";
import type { PatternChar } from "react-native-haptic-feedback";

const valid = [...input].every((c) => PATTERN_CHARS.has(c as PatternChar));

Built-in Presets

import { Patterns } from "react-native-haptic-feedback";

RNHapticFeedback.triggerPattern(Patterns.success);
RNHapticFeedback.triggerPattern(Patterns.error);
RNHapticFeedback.triggerPattern(Patterns.warning);
RNHapticFeedback.triggerPattern(Patterns.heartbeat);
RNHapticFeedback.triggerPattern(Patterns.tripleClick);
RNHapticFeedback.triggerPattern(Patterns.notification);

useHaptics Hook

import { useHaptics } from "react-native-haptic-feedback";

function MyButton() {
  const haptics = useHaptics({ enableVibrateFallback: true });

  return (
    <Pressable onPress={() => haptics.trigger('impactMedium')}>
      Press me
    </Pressable>
  );
}

The hook accepts default options that are merged with per-call overrides.


TouchableHaptic Component

A drop-in Pressable wrapper that automatically triggers haptic feedback. Accepts all standard PressableProps plus three extra props:

Prop Type Default Description
hapticType HapticFeedbackTypes impactMedium Feedback type to play
hapticTrigger 'onPressIn' | 'onPress' | 'onLongPress' onPressIn Which event triggers haptics
hapticOptions HapticOptions Options forwarded to trigger()
import { TouchableHaptic } from "react-native-haptic-feedback";

<TouchableHaptic
  hapticType="impactMedium"
  hapticTrigger="onPressIn"
  onPress={handlePress}
  style={styles.button}
>
  <Text>Press me</Text>
</TouchableHaptic>

Available Feedback Types

Type Android iOS Notes
impactLight API 31+: PRIMITIVE_TICK
impactMedium API 31+: PRIMITIVE_CLICK
impactHeavy API 31+: PRIMITIVE_HEAVY_CLICK
rigid API 31+: PRIMITIVE_CLICK (scale 0.9)
soft API 31+: PRIMITIVE_TICK (scale 0.3)
notificationSuccess
notificationWarning
notificationError
selection
confirm Android API 30+: CONFIRM; fallback waveform on older
reject Android API 30+: REJECT; fallback waveform on older
gestureStart Android API 30+: GESTURE_START; fallback waveform on older
gestureEnd Android API 30+: GESTURE_END; fallback waveform on older
segmentTick Android API 30+: SEGMENT_TICK; fallback waveform on older
segmentFrequentTick Android API 30+: SEGMENT_FREQUENT_TICK; fallback waveform on older
toggleOn Android API 34+: TOGGLE_ON; fallback waveform on older
toggleOff Android API 34+: TOGGLE_OFF; fallback waveform on older
dragStart Android API 34+: DRAG_START; fallback waveform on older
gestureThresholdActivate Android API 34+: GESTURE_THRESHOLD_ACTIVATE; fallback on older
gestureThresholdDeactivate Android API 34+: GESTURE_THRESHOLD_DEACTIVATE; fallback on older
noHaptics Android API 34+: NO_HAPTICS (explicit no-op)
clockTick iOS: Core Haptics approximation
contextClick iOS: Core Haptics approximation
keyboardPress iOS: Core Haptics approximation
keyboardRelease iOS: Core Haptics approximation
keyboardTap iOS: Core Haptics approximation
longPress iOS: Core Haptics approximation
textHandleMove iOS: Core Haptics approximation
virtualKey iOS: Core Haptics approximation
virtualKeyRelease iOS: Core Haptics approximation
effectClick Android API 29+; iOS: Core Haptics approximation
effectDoubleClick Android API 29+; iOS: Core Haptics approximation
effectHeavyClick Android API 29+; iOS: Core Haptics approximation
effectTick Android API 29+; iOS: Core Haptics approximation

For the complete API reference including all types, options, and examples, see the documentation site.


Platform internals

Understanding how each haptic type is rendered helps when diagnosing unexpected behaviour on specific devices.

iOS — three-tier fallback chain

Every trigger() call on iOS walks this chain and stops at the first tier that succeeds:

Tier Hardware requirement What fires
1 — Core Haptics iPhone 8+ / iPad Pro (iOS 13+) CHHapticEngine — full per-type patterns with custom intensity & sharpness
2 — UIKit generators Taptic Engine (iPhone 6s, 7, SE 1st gen on iOS 13+) UIImpactFeedbackGenerator / UINotificationFeedbackGenerator / UISelectionFeedbackGenerator — per-type, semantically mapped
3 — Audio vibration Any device AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)only fires if enableVibrateFallback: true

Tier 3 exists for devices with no Taptic Engine at all (e.g. iPod touch 7th gen). On any device with a Taptic Engine, Tier 2 handles it and Tier 3 is unnecessary.

UIKit semantic mapping (Tier 2):

UIKit generator Types
UINotificationFeedbackGenerator(.success) notificationSuccess
UINotificationFeedbackGenerator(.warning) notificationWarning
UINotificationFeedbackGenerator(.error) notificationError, reject
UIImpactFeedbackGenerator(.light) impactLight, soft, effectTick, clockTick, gestureStart, segmentTick, segmentFrequentTick, textHandleMove
UIImpactFeedbackGenerator(.medium) impactMedium, confirm, toggleOn, toggleOff, effectClick, effectDoubleClick and all other types
UIImpactFeedbackGenerator(.heavy) impactHeavy, rigid, effectHeavyClick, longPress
UISelectionFeedbackGenerator selection, keyboardPress, keyboardRelease, keyboardTap, virtualKey, virtualKeyRelease, gestureEnd, contextClick

Android — two-tier fallback chain

Tier API level What fires
1 — performHapticFeedback All (via HapticFeedbackConstants) System-quality haptic constant via the activity's decorView — respects user system settings unless ignoreAndroidSystemSettings: true
2 — Vibrator API API 23+ VibrationEffect.createWaveform (API 26+) or raw waveform; API 31+ uses VibrationEffect.Composition primitives for richer quality

Tier 1 is skipped when ignoreAndroidSystemSettings: true (because performHapticFeedback cannot override system settings), falling directly to Tier 2.

Android API-level progression:

API Improvement
23 Raw waveform vibration (minimum supported)
26 VibrationEffect.createWaveform with per-step amplitudes
29 VibrationEffect.createPredefined for effect* types
30 HapticFeedbackConstants for confirm, reject, gesture*, segment*
31 VibrationEffect.Composition primitives (richer impact feel)
33 VibrationAttributes.USAGE_TOUCH — vibrations respect system haptic-preference settings
34 HapticFeedbackConstants for toggleOn, toggleOff, dragStart, gestureThreshold*, noHaptics

Jest Mock

// In your test file:
jest.mock("react-native-haptic-feedback");

// All methods are automatically mocked:
// trigger, stop, isSupported (→ true), triggerPattern, playAHAP (→ Promise.resolve()),
// getSystemHapticStatus (→ { vibrationEnabled: true, ringerMode: 'normal' } on Android, ringerMode: null on iOS),
// useHaptics, pattern, Patterns

Migrating from v2 to v3

Breaking changes

  1. iOS minimum target is now 13.0 — remove any <13.0 deployment-target overrides.
  2. React Native minimum is 0.71 — update your peer dependency if needed.
  3. The internal DeviceUtils class is removed — if you referenced it directly, remove those imports.
  4. enableVibrateFallback on devices without Core Haptics now calls kSystemSoundID_Vibrate instead of the UIKit generator path.

Upgrade steps

npm install react-native-haptic-feedback@latest
cd ios && pod install

All existing trigger() call-sites continue to work without changes. The new confirm, reject, gestureStart/End, segmentTick/FrequentTick, toggleOn/Off types are additive.

See the full migration guide in the docs.


Acknowledgements

  • expo-ahap by @EvanBacon — the AhapType TypeScript definitions exported by this library are modelled after the types in expo-ahap.
  • All contributors who submitted issues and pull requests.

Troubleshooting

Haptics not firing on iOS simulator — Core Haptics does not work in the iOS Simulator. Test on a physical device.

isSupported() returns false — the device does not have a Taptic Engine (iPhone 7 or older without the A9 chip, or iPads). Use enableVibrateFallback: true to fall back to system vibration.

Android vibration seems weak — upgrade your target device to API 31+ and ensure the device has a high-quality actuator. Use triggerPattern with explicit amplitudes for more control.

About

Haptics that feel right: Core Haptics, AHAP files, pattern notation, and cross-platform utilities for iOS and Android.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

  •  

Contributors