expo-haptics but better (support for custom haptics and patterns)
npm install expo-better-hapticsA drop-in replacement for expo-haptics offering fine-grained control over haptic feedback on iOS and Android devices.
- 🤖 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
- 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
``bash`
npx expo install expo-better-haptics
`javascript
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
})
`
#### Custom Transient Haptics
`javascript
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
`javascript
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 seconds
`
#### Custom Haptic Patterns
`javascript
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:
`javascript
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:
`javascript
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.
`javascript
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()
}
`
- isSupported - Boolean indicating if haptics are supported on the device
#### 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 MediumnotificationAsync(type?)
- - Plays a notification haptic with the specified typetype
- (NotificationFeedbackType) - Type of notification, defaults to SuccessselectionAsync()
- - 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.5playTransientAsync(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 effectplayContinuousAsync(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 secondsplayPatternAsync(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 occurcreateContinuousEvent(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
- ImpactFeedbackStyle - Enum for impact feedback styles
- Light - 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
- HapticEventType - Enum for haptic event typesHapticEventParameterType
- - Enum for haptic event parameter typesHapticEvent
- - Interface for haptic eventsHapticParameter
- - Interface for haptic parameters
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
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 | 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 | ✗ | ✗ |
expo-better-haptics offers more capabilities over the standard expo-haptics:
1. Fine-grained control: Adjust intensity and sharpness parameters on both iOS and Android
2. Continuous haptics: Standard haptics only offers preset impacts
3. Complex patterns: Create sequences of haptic events with precise timing
4. Longer effects: Create sustained haptic experiences of any duration
5. Cross-platform: Full Android support with proper haptic implementations
This library is fully compatible with expo-haptics, making migration easy:
`javascript
// Replace this:
import * as Haptics from 'expo-haptics'
// With this:
import * as Haptics from 'expo-better-haptics'
`
All existing code will continue to work:
`javascript
// 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:
`javascript`
// 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)
The module includes an example app that demonstrates all the haptic capabilities:
`bash``
cd example
npx expo run:android --device # For Android testing
npx expo run:ios --device # For iOS testing
> Note: Must be run on a physical device to feel the haptic feedback.
The Android implementation uses a tiered approach to provide the best haptic experience based on the device's capabilities:
1. Android 11+ (API 30+): Uses Haptic Composition API with primitives like TICK, CLICK, THUD, QUICK_RISE, etc.
2. Android 8-10 (API 26-29): Uses VibrationEffect API with amplitude control and waveform patterns
3. 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
- 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
This library was co-authored by:
- Carter (@carter-0)
- Claude Code
MIT