Expoexpo-hapticsUXReact Native Components

Expo Haptics Guide: Add Vibration and Haptic Feedback to React Native Apps

Add native haptic feedback to Expo apps with expo-haptics — impactAsync, notificationAsync, selectionAsync, and Android-specific performAndroidHapticsAsync. Includes a reusable haptics hook, UX rules, and iOS/Android caveats.

DeveloperMill4 min read

Haptics are the cheapest way to make a React Native app feel native. A light tap when a button is pressed, a crisp tick when a picker snaps, a success buzz when a payment goes through. Users rarely notice good haptics, but they notice when an app doesn't have them. expo-haptics gives you the same feedback patterns iOS and Android use in their own apps, in one line of code.

Install

npx expo install expo-haptics

It's included in Expo Go, so you can try it right away on a real device. The iOS Simulator has no Taptic Engine.

The three core haptics

1. Impact: for taps and collisions

import * as Haptics from "expo-haptics"

await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)

Styles: Light, Medium (default), Heavy, Rigid, and Soft. Use Light or Soft for buttons and Medium or Heavy for meaningful physical moments like a card snapping into place or a long-press activating.

2. Selection: for value changes

await Haptics.selectionAsync()

This is the subtle tick you feel when scrolling an iOS picker wheel. Use it for pickers, sliders, segmented controls, toggles, and tab changes, anywhere a value changes step by step.

3. Notification: for outcomes

await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)

Types: Success, Warning, and Error. Use them for the result of an action: a saved form, a failed login, a destructive action that needs confirmation.

Android-native haptics

Android has its own vocabulary of haptic constants. performAndroidHapticsAsync uses the device haptics engine directly and doesn't need the VIBRATE permission:

import { Platform } from "react-native"
import * as Haptics from "expo-haptics"

if (Platform.OS === "android") {
  await Haptics.performAndroidHapticsAsync(Haptics.AndroidHaptics.Clock_Tick)
}

Useful constants include Clock_Tick (picker ticks), Confirm, Reject, Toggle_On, Toggle_Off, Long_Press, Segment_Tick, Gesture_Start, and Gesture_End. The docs recommend this over Android's Vibrator API for UI feedback.

A reusable haptics helper

Centralize haptics so the whole app feels consistent, and so you can respect a user setting to turn them off:

// lib/haptics.ts
import { Platform } from "react-native"
import * as Haptics from "expo-haptics"

let enabled = true
export const setHapticsEnabled = (value: boolean) => {
  enabled = value
}

export const haptics = {
  tap: () => enabled && Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light),
  select: () => {
    if (!enabled) return
    return Platform.OS === "android"
      ? Haptics.performAndroidHapticsAsync(Haptics.AndroidHaptics.Clock_Tick)
      : Haptics.selectionAsync()
  },
  success: () => enabled && Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success),
  warning: () => enabled && Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning),
  error: () => enabled && Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error),
}

Store the user's preference with MMKV and call setHapticsEnabled on startup.

A haptic button

import { Pressable, Text } from "react-native"
import { haptics } from "@/lib/haptics"

export function HapticButton({ label, onPress }: { label: string; onPress: () => void }) {
  return (
    <Pressable
      onPressIn={haptics.tap}
      onPress={onPress}
      className="rounded-2xl bg-violet-600 px-5 py-3 active:opacity-80"
    >
      <Text className="text-center font-semibold text-white">{label}</Text>
    </Pressable>
  )
}

Fire the haptic on onPressIn, not onPress. Feedback should land the moment the finger touches the screen, not when it lifts. (Styled with NativeWind.)

Haptic UX rules

  • Match the haptic to the meaning: selection for change, impact for physical contact, notification for outcomes.
  • Less is more. Don't buzz on every tap in a scrolling list. Reserve haptics for intentional actions.
  • Pair with motion. A haptic should land at the exact frame an animation "hits", like a sheet snapping or confetti bursting.
  • Never use haptics as the only signal. Some users have them disabled, so always pair them with a visual change.
  • Offer a setting to turn them off.

Platform caveats

On iOS, haptics silently do nothing when Low Power Mode is on, the user disabled system haptics, the camera is active, or dictation is running. You can detect Low Power Mode with expo-battery.

On the web, expo-haptics falls back to the Vibration API, which needs browser support and vibration hardware. In practice, that means Android Chrome. Calls are safe everywhere, so you don't need to guard them.

See it in real components

Haptics are built into several of our free, MIT-licensed components:

  • Arc Wheel Picker uses selectionAsync on iOS and Clock_Tick on Android for every snap detent.
  • Gradient Background adds a spring press-scale plus expo-haptics on every tile.
  • Expo Confetti pairs well with a Success notification haptic on a completed action.

Want more device feedback? Combine haptics with Reanimated 4 animations, and see the rest of the top Expo SDK modules.

Frequently asked questions

How do I add haptic feedback in Expo?

Install expo-haptics with `npx expo install expo-haptics`, then call `Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)` for taps, `Haptics.selectionAsync()` for picker or toggle changes, and `Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)` for outcomes like a completed payment.

Why are haptics not working on my iPhone?

iOS disables the Taptic Engine when Low Power Mode is on, when the user has turned off system haptics, while the camera is active, and during dictation. Haptics also do nothing in the iOS Simulator — always test on a real device.

Does expo-haptics need the VIBRATE permission on Android?

Not when you use `performAndroidHapticsAsync`, which uses the device haptics engine and doesn't require the VIBRATE permission. The docs recommend it over Android's Vibrator API for UI feedback.

Does expo-haptics work on the web?

Yes, on supported browsers. On the web it uses the Vibration API, which requires browser support and vibration hardware, and some browsers ignore it in background tabs. Desktop browsers and iOS Safari generally won't vibrate.

Build it faster with a template

Production-ready Expo React Native templates with the patterns from this post already wired up.

Browse templates