React Native
What Is the Difference Between iOS and Android Haptics
Krzysztof PiaskowyKrzysztof PiaskowyJun 18, 20266 min read

Haptic feedback on iOS and Android works differently. Fundamentally differently, actually, at the hardware and API level. If you're building for both platforms, that's something you need to account for before you start designing patterns, not after. Here's what's actually going on.

The hardware haptics story

Apple designed the Taptic Engine from scratch for one purpose. Because they also control the entire stack (hardware, driver, and API) they can tightly integrate every layer of the system. The result is millisecond-level precision and predictable output across every iPhone your user might be holding.

On the other end of the spectrum, Android is more like an ecosystem. Samsung, Pixel, Xiaomi, OnePlus – every manufacturer picks their own actuator, their own quality of LRA or ERM motor. Though they use the same code, they have a completely different sensation depending on the device. That’s why, there is no single “Android haptics”, there are dozens of versions of it.

Here's what that actually means in practice: an LRA motor has one resonant frequency – the point at which it produces maximum amplitude. When you stray from that frequency, the output weakens. It's like dribbling a basketball: there's one rhythm where the ball bounces cleanly, and anything off that rhythm loses energy. Apple's Taptic Engine is engineered around that sweet spot. On Android, the resonant frequency (and the precision around it) varies by manufacturer and price point.

The Android documentation covers this in detail – it’s worth reading before you design anything for Android.

The software haptics story

On iOS, the Core Haptics API gives you full control: intensity, sharpness, timing, custom patterns. It's a real API for composing haptic experiences.

// import CoreHaptics
let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.8)
let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0)
let event = CHHapticEvent(
    eventType: .hapticTransient,
    parameters: [intensity, sharpness],
    relativeTime: 0
)
let pattern = try CHHapticPattern(events: [event], parameters: [])
let player = try CHHapticEngine().makePlayer(with: pattern)
try player?.start(atTime: 0)

Android's history here is… more complicated. The original Vibrator API and HapticFeedbackConstants were simple and limited – this is what "haptics" looked like for a long time:

// legacy API
val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
vibrator.vibrate(200) // vibrate for 200ms. That's it.
The VibrationEffect API arrived in Android 8.0 and added basic amplitude and timing control:
// VibrationEffect (API 26+)
val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
val effect = VibrationEffect.createOneShot(
    200,
    VibrationEffect.DEFAULT_AMPLITUDE
)
vibrator.vibrate(effect)

Compositions came in Android 11 and this is where it starts to look more like what iOS has had for years:

// VibrationEffect.Composition (API 30+)
val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
val composition = VibrationEffect.startComposition()
    .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1.0f)
    .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 50)
    .compose()
vibrator.vibrate(composition)

The newest addition, the Envelope API, is the closest Android has come to what Core Haptics offers:

// Envelope API (API 34+)
val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
val envelope = VibrationEffect.createWaveform(
    longArrayOf(0, 100, 50, 100),
    intArrayOf(0, 255, 0, 128),
    -1
)
vibrator.vibrate(envelope)

The Android haptics API overview maps it all out. The short version: on iOS, you design a pattern. On Android, for a long time, you could only turn the vibration on or off. That's changing, but slowly, and unevenly across devices.

The fragmentation problem

The main problem with Android haptics is the fact that you can write a great haptic pattern and still will have no idea because haptics can differ from phone model to phone model tremendously. Pixel 8 brings a different experience from a mid-range Xiaomi. It goes deeper than hardware: Android has system-level haptic presets, but many of them are interpreted by the hardware manufacturer or OS vendor, which means the same preset can vibrate completely differently across devices.

This makes haptics particularly challenging in cross-platform applications. Unlike visual UI components, haptic feedback is hard to evaluate without testing on a physical device – after all, you can’t really tell how it feels from a simulator or code review alone.

On top of that, iOS and Android offer different haptic capabilities and rely on different hardware, so the same interaction can feel noticeably different across platforms and even devices. As a result, delivering a consistent haptic experience often requires platform-specific testing and, in many cases, platform-specific tuning.

 iOSAndroid
HardwareTaptic Engine – one standard across all iPhonesEach manufacturer chooses their own actuator, LRA or ERM
Main APICore Haptics APIVibrationEffect API (Android 8.0+), compositions (Android 11+), Envelope API
Developer controlHigh – predictable output across all devicesVaries – depends on device and Android version
FragmentationNoneHigh – same code, different experience across devices
Fallback behaviorAutomaticNone – unsupported compositions simply won't play
Timing precision~5ms~50ms

What Pulsar does with haptics on Android

Android has made real progress – the gap is much smaller than it used to be. But fragmentation doesn't disappear just because the APIs got better. The same pattern can behave completely differently depending on the device, the Android version, and what the manufacturer decided to support.

Pulsar handles that complexity automatically. Instead of checking at runtime what each device supports and branching your code accordingly, Pulsar detects the available capabilities and uses the best one. If a device supports frequency modulation, it uses it. If it doesn't, it falls back gracefully, without you having to write any of that logic yourself. If a system preset isn't available, there's a custom implementation ready to step in.

There's also a testing feature worth knowing about. If you're developing on a Pixel – which has some of the best haptic hardware on Android – you can force Pulsar to simulate how a pattern would feel on a lower-end device, without physically switching phones. That's the kind of thing that usually only surfaces as a user complaint after you've already shipped.

And because haptics is something you feel rather than see, there's no substitute for testing on real hardware before you ship. That's why Pulsar ships with Live Preview – 150+ presets you can feel on your actual device before committing to any of them.

Here's what it looks like in practice:

As you see, the gap between iOS and Android haptics is real. Building for both platforms means taking those differences into account early – in how you design patterns, which APIs you target, and how you test before shipping. It’s up to you whether you design around it, or discover it in production.