expo-better-haptics
v1.0.2
Published
expo-haptics but better (support for custom haptics and patterns)
Maintainers
Readme
Expo Better Haptics
A drop-in replacement for expo-haptics offering fine-grained control over haptic feedback on iOS and Android devices.
Features
- 🤖 Better Android support using dedicated Haptics APIs
- 💿 Drop-in replacement for
expo-haptics - ⚡ Continuous or transient impacts with multiple intensity levels
- 🎵 Complex haptic patterns with precise timing and sequencing
- 💥 Pre-built notification patterns (success, warning, error)
- 📱 Cross-platform support for both iOS and Android
- 🎼 AHAP file support for playing Apple Haptic Audio Patterns on iOS
Requirements
- Expo SDK 52 or newer
- iOS 13+ for CoreHaptics API (iPhone 8 or newer)
- Android 8+ (API level 26) for enhanced vibration effects
- Android 11+ (API level 30) for Haptic Compositions and Primitives
Installation
npx expo install expo-better-hapticsUsage
Basic Usage
import * as Haptics from 'expo-better-haptics'
// Play impact haptics
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium)
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy)
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Soft)
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Rigid)
// Play notification haptics
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning)
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error)
// Play selection feedback
await Haptics.selectionAsync()
// Play a continuous vibration
await Haptics.vibrateAsync({
intensity: 0.8, // 0-1, defaults to 0.8
sharpness: 0.5, // 0-1, defaults to 0.5
duration: 0.5, // in seconds, defaults to 0.5
})Advanced Usage
Custom Transient Haptics
import * as Haptics from 'expo-better-haptics'
// Play a custom transient haptic with specific intensity and sharpness
await Haptics.playTransientAsync(0.7, 0.9)Custom Continuous Haptics
import * as Haptics from 'expo-better-haptics'
// Play a continuous haptic with specific intensity, sharpness, and duration
await Haptics.playContinuousAsync(0.6, 0.3, 1.2) // 1.2 secondsCustom Haptic Patterns
import * as Haptics from 'expo-better-haptics'
import { HapticEvent, HapticEventType, HapticEventParameterType } from 'expo-better-haptics'
// Create a custom pattern using helper methods
const customPattern = [
// First tap at time 0
Haptics.createTransientEvent({
intensity: 0.8,
sharpness: 0.5,
time: 0,
}),
// Second tap after a short pause
Haptics.createTransientEvent({
intensity: 0.5,
sharpness: 0.3,
time: 0.15,
}),
// Longer continuous effect after another pause
Haptics.createContinuousEvent({
intensity: 1.0,
sharpness: 0.7,
time: 0.4,
duration: 0.3,
}),
]
// Play the custom pattern
await Haptics.playPatternAsync(customPattern)
// Or create patterns manually with full control
const manualPattern = [
{
type: HapticEventType.Transient,
time: 0,
parameters: [
{ id: HapticEventParameterType.Intensity, value: 0.8 },
{ id: HapticEventParameterType.Sharpness, value: 0.5 },
],
},
{
type: HapticEventType.Continuous,
time: 0.2,
duration: 0.5,
parameters: [
{ id: HapticEventParameterType.Intensity, value: 0.6 },
{ id: HapticEventParameterType.Sharpness, value: 0.3 },
],
},
]
await Haptics.playPatternAsync(manualPattern)Playing AHAP Files (iOS Only)
AHAP (Apple Haptic Audio Pattern) files allow you to create rich haptic experiences with synchronized audio on iOS:
import * as Haptics from 'expo-better-haptics'
// Play an AHAP pattern directly from JSON
const ahapPattern = {
Pattern: [
{
Event: {
Time: 0.0,
EventType: "HapticContinuous",
EventDuration: 0.6,
EventParameters: [
{ ParameterID: "HapticIntensity", ParameterValue: 1.0 },
{ ParameterID: "HapticSharpness", ParameterValue: 0.5 },
],
},
},
{
Event: {
Time: 0.601,
EventType: "HapticTransient",
EventParameters: [
{ ParameterID: "HapticIntensity", ParameterValue: 1.0 },
{ ParameterID: "HapticSharpness", ParameterValue: 0.7 },
],
},
},
],
}
await Haptics.playAHAPAsync(ahapPattern)
// Or load from an imported JSON file
import heartbeatPattern from './haptics/heartbeat.json'
await Haptics.playAHAPAsync(heartbeatPattern)
// You can also pass AHAP as a JSON string
const ahapString = JSON.stringify(ahapPattern)
await Haptics.playAHAPAsync(ahapString)AHAP files support parameter curves for dynamic control:
const dynamicPattern = {
Pattern: [
{
Event: {
Time: 0.0,
EventType: "HapticContinuous",
EventDuration: 1.0,
EventParameters: [
{ ParameterID: "HapticIntensity", ParameterValue: 0.5 },
{ ParameterID: "HapticSharpness", ParameterValue: 0.5 },
],
},
},
{
ParameterCurve: {
ParameterID: "HapticIntensityControl",
Time: 0.0,
ParameterCurveControlPoints: [
{ Time: 0, ParameterValue: 0.2 },
{ Time: 0.5, ParameterValue: 1.0 },
{ Time: 1.0, ParameterValue: 0.2 },
],
},
},
],
}
await Haptics.playAHAPAsync(dynamicPattern)Note: AHAP patterns are only supported on iOS 13+ devices. On Android, the playAHAPAsync method will fail silently.
Checking Support and Managing Engine
import * as Haptics from 'expo-better-haptics'
// Check if haptics are supported on the device
const isSupported = Haptics.isSupported
if (isSupported) {
// Explicitly initialize the haptics engine (optional - will auto-initialize when needed)
await Haptics.initialize()
// Do haptic operations...
// When done, you can explicitly stop the engine (optional)
await Haptics.stop()
}API Reference
Constants
isSupported- Boolean indicating if haptics are supported on the device
Methods
Initialization
initialize()- Explicitly initializes the haptic engine (auto-initialized when needed)start()- Explicitly starts the haptic enginestop()- Explicitly stops the haptic engine
Standard Haptics
impactAsync(style?)- Plays an impact haptic with the specified stylestyle(ImpactFeedbackStyle) - Style of impact, defaults to Medium
notificationAsync(type?)- Plays a notification haptic with the specified typetype(NotificationFeedbackType) - Type of notification, defaults to Success
selectionAsync()- Plays a selection haptic
Advanced Haptics
vibrateAsync(options?)- Plays a customizable continuous vibrationoptions.intensity(number, 0-1) - Intensity of the vibration, defaults to 0.8options.sharpness(number, 0-1) - Sharpness of the vibration, defaults to 0.5options.duration(number) - Duration in seconds, defaults to 0.5
playTransientAsync(intensity, sharpness)- Plays a transient haptic with customized parametersintensity(number, 0-1) - Required. Intensity of the haptic effectsharpness(number, 0-1) - Required. Sharpness of the haptic effect
playContinuousAsync(intensity, sharpness, duration)- Plays a continuous haptic with customized parametersintensity(number, 0-1) - Required. Intensity of the haptic effectsharpness(number, 0-1) - Required. Sharpness of the haptic effectduration(number) - Required. Duration in seconds
playPatternAsync(events)- Plays a custom haptic pattern defined by an array of haptic eventsplayAHAPAsync(ahapData)- Plays an AHAP (Apple Haptic Audio Pattern) on iOS devicesahapData(Object or string) - AHAP pattern data as a JavaScript object or JSON string
Helper Methods
createTransientEvent(options)- Creates a transient haptic event objectoptions.intensity(number, 0-1) - Intensity of the haptic effectoptions.sharpness(number, 0-1) - Sharpness of the haptic effectoptions.time(number) - Time offset in seconds for when this event should occur
createContinuousEvent(options)- Creates a continuous haptic event objectoptions.intensity(number, 0-1) - Intensity of the haptic effectoptions.sharpness(number, 0-1) - Sharpness of the haptic effectoptions.time(number) - Time offset in seconds for when this event should occuroptions.duration(number) - Duration of the continuous effect in seconds
Enums
ImpactFeedbackStyle- Enum for impact feedback stylesLight- A collision between small, light user interface elementsMedium- A collision between moderately sized user interface elementsHeavy- A collision between large, heavy user interface elementsRigid- A collision between user interface elements that are rigidSoft- A collision between user interface elements that are soft
NotificationFeedbackType- Enum for notification feedback typesSuccess- A notification feedback type indicating that a task has completed successfullyWarning- A notification feedback type indicating that a task has produced a warningError- A notification feedback type indicating that a task has failed
Types
HapticEventType- Enum for haptic event typesHapticEventParameterType- Enum for haptic event parameter typesHapticEvent- Interface for haptic eventsHapticParameter- Interface for haptic parameters
Platform-Specific Implementation
iOS
On iOS, this module uses:
- CoreHaptics API for fine-grained haptic control (iOS 13+)
- UIImpactFeedbackGenerator for impact feedback
- UINotificationFeedbackGenerator for notification feedback
- UISelectionFeedbackGenerator for selection feedback
Android
On Android, this module uses:
- Haptic Compositions API with primitives for rich haptic feedback (Android 11+)
- VibrationEffect API for amplitude control and waveforms (Android 8+)
- View.performHapticFeedback for simple haptic feedback
- Intelligent fallbacks for older devices
Feature Compatibility by Platform
| Feature | iOS | Android 11+ | Android 8-10 | Android <8 | | ------------------------- | --- | ----------- | ------------ | ---------- | | Impact feedback | ✓ | ✓ | ✓ | Limited | | Notification patterns | ✓ | ✓ | ✓ | Limited | | Selection feedback | ✓ | ✓ | ✓ | ✓ | | Intensity control | ✓ | ✓ | ✓ | ✗ | | Sharpness control | ✓ | Limited | ✗ | ✗ | | Custom patterns | ✓ | ✓ | Limited | ✗ | | Continuous effects | ✓ | ✓ | ✓ | Limited | | Dynamic parameter control | ✓ | Limited | ✗ | ✗ |
Compared to Expo Haptics
expo-better-haptics offers more capabilities over the standard expo-haptics:
- Fine-grained control: Adjust intensity and sharpness parameters on both iOS and Android
- Continuous haptics: Standard haptics only offers preset impacts
- Complex patterns: Create sequences of haptic events with precise timing
- Longer effects: Create sustained haptic experiences of any duration
- Cross-platform: Full Android support with proper haptic implementations
API Compatibility with expo-haptics
This library is fully compatible with expo-haptics, making migration easy:
// Replace this:
import * as Haptics from 'expo-haptics'
// With this:
import * as Haptics from 'expo-better-haptics'All existing code will continue to work:
// Standard expo-haptics API
await Haptics.impactAsync() // Default medium impact
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium)
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy)
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Rigid)
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Soft)
await Haptics.notificationAsync() // Default success notification
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning)
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error)
await Haptics.selectionAsync()In addition, you'll gain access to more powerful haptic features:
// Extended functionality
await Haptics.vibrateAsync({ intensity: 0.8, sharpness: 0.6, duration: 0.5 })
await Haptics.playTransientAsync(0.7, 0.9)
await Haptics.playContinuousAsync(0.6, 0.3, 1.2)
await Haptics.playPatternAsync(customPattern)Example App
The module includes an example app that demonstrates all the haptic capabilities:
cd example
npx expo run:android --device # For Android testing
npx expo run:ios --device # For iOS testingNote: Must be run on a physical device to feel the haptic feedback.
Android Implementation Details
The Android implementation uses a tiered approach to provide the best haptic experience based on the device's capabilities:
- Android 11+ (API 30+): Uses Haptic Composition API with primitives like TICK, CLICK, THUD, QUICK_RISE, etc.
- Android 8-10 (API 26-29): Uses VibrationEffect API with amplitude control and waveform patterns
- Older Android versions: Falls back to basic vibration patterns
The implementation intelligently maps iOS haptic concepts to their Android equivalents:
- Intensity: Maps to vibration amplitude (0-255)
- Sharpness: Maps to different primitive types or vibration duration
- Patterns: Converted to appropriate waveform patterns
Limitations
- iOS CoreHaptics requires iOS 13+ and iPhone 8 or newer
- Android haptic primitives require Android 11+ (API level 30)
- Must be run on a physical device to feel the haptics
- Haptics may not work if device is in silent mode, battery saver mode, or has haptics disabled
- Some Android devices have limited haptic capability hardware
Authors
This library was co-authored by:
- Carter (@carter-0)
- Claude Code
License
MIT
