haptic_patterns
Composable haptic feedback patterns for Flutter — chain, repeat, and compose taps, buzzes, and custom sequences on Android and iOS.
Most haptics plugins give you a single vibrate() call. haptic_patterns
gives you a small DSL for describing what feedback should actually feel
like — a sequence of pulses, a repeated buzz, a fading curve — and maps it
onto each platform's real native haptic engine: Android VibrationEffect
compositions and iOS CHHapticEngine patterns. Describe the feel once; it
renders correctly on both.
import 'package:haptic_patterns/haptic_patterns.dart';
// A ready-made preset.
HapticPatterns.success.play();
// Or build your own from scratch.
HapticSequence([
HapticEvent.tap(),
delay(50),
HapticEvent.tap(),
delay(50),
HapticEvent.thud(),
]).play();
Installation
flutter pub add haptic_patterns
No further setup needed — Android and iOS both work out of the box.
Quickstart
import 'package:haptic_patterns/haptic_patterns.dart';
// Play a single tap.
HapticEvent.tap().play();
// Play one of 25 built-in presets.
HapticPatterns.celebration.play();
// Wrap a widget so a gesture plays a pattern automatically.
HapticFeedbackWidget(
tapPattern: HapticPatterns.selection,
onTap: () => setState(() => count++),
child: MyButton(),
)
The pattern DSL
Every pattern is a HapticPattern. They compose freely, since a
composed pattern is itself a HapticPattern:
| Type | What it does |
|---|---|
HapticEvent |
A single pulse. .tap(), .thud(), and .buzz() are ready-made shapes; the base constructor takes intensity (0.0–1.0), sharpness (0.0–1.0), and duration (Duration.zero for a transient tap). |
HapticSequence([...]) |
Plays a list of patterns in order. Use delay(ms) between entries to space them out. |
HapticRepeat(pattern, count:, gap:) |
Repeats a pattern count times with gap between repetitions. |
HapticCurve(startIntensity:, endIntensity:, duration:, steps:) |
Approximates a smooth intensity ramp/fade as closely-spaced discrete pulses — the same instruction format works on both platforms without native-only APIs. |
HapticDelay / delay(ms) |
A pause with no haptic output. Only meaningful inside a HapticSequence, as a gap before whatever follows it. |
Every pattern reduces to a flat List<HapticInstruction> via
.compile(), which is what actually crosses the platform channel —
composing patterns is purely a Dart-side concern; native code never has
to understand HapticSequence or HapticRepeat as such.
// Three buzzes, 80ms apart.
HapticRepeat(HapticEvent.buzz(), count: 3, gap: Duration(milliseconds: 80));
// A fade-out over 200ms.
HapticCurve(startIntensity: 0.8, endIntensity: 0.0, duration: Duration(milliseconds: 200));
Playing patterns
await HapticPlayer.play(pattern); // equivalent to pattern.play()
await HapticPlayer.stop(); // stop whatever's currently playing
await HapticPlayer.isSupported(); // whether the device has haptic hardware
HapticPattern.play() is shorthand for HapticPlayer.play(this) — most
call sites read more naturally as HapticEvent.tap().play(). Reach for
HapticPlayer directly when you already hold a compiled pattern
reference, or want .stop() / .isSupported().
HapticFeedbackWidget
Wraps a child with tap/long-press/double-tap detection that plays a pattern alongside — not instead of — a normal callback:
HapticFeedbackWidget(
tapPattern: HapticPatterns.tick,
onTap: () => print('tapped'),
longPressPattern: HapticPatterns.warning,
onLongPress: showDeleteConfirmation,
child: MyButton(),
)
Each gesture wires up independently: a widget with only
longPressPattern set doesn't swallow its taps, and one with only a
callback (no pattern) plays nothing but still calls back normally. It
uses GestureDetector under the hood, so it works with Material,
Cupertino, or plain widgets — the widget itself only imports
package:flutter/widgets.dart.
Pattern catalog
HapticPatterns ships 25 ready-to-use presets. HapticPatterns.all is a
Map<String, HapticPattern> of all of them, handy for building a picker
UI (see the example app's Playground tab).
System-feedback-style
| Preset | Feel |
|---|---|
success |
A firm tap followed by a lighter confirming pulse. |
error |
An insistent double-buzz. |
warning |
A moderate double-tap — needs attention, less severe than error. |
selection |
A very light, quick tap — scroll detents, picker changes, toggles. |
notification |
A distinct three-pulse pattern for incoming alerts. |
Rhythmic
| Preset | Feel |
|---|---|
heartbeat |
A strong "lub" followed closely by a softer "dub". |
tick |
A single minimal tap — clock tick, scroll wheel detent. |
doubleTick |
Two quick ticks close together. |
heavyClick |
One strong, sharp click — button press, toggle confirm. |
softTap |
A gentle, low-intensity tap — a subtle acknowledgement. |
Sustained
| Preset | Feel |
|---|---|
buzz |
A short sustained buzz. |
longBuzz |
A longer sustained buzz — e.g. a long-press acknowledgement. |
pulse |
A rhythmic on-off pulse, three beats. |
Dynamics
| Preset | Feel |
|---|---|
rampUp |
Three taps of increasing intensity — building anticipation. |
rampDown |
Three taps of decreasing intensity — winding down. |
swoosh |
A smooth buzz that fades toward the end (HapticCurve). |
Expressive
| Preset | Feel |
|---|---|
confirm |
A single crisp confirming tap. |
cancel |
A blunt, dismissive single thud. |
alert |
A sharp triple-tap for grabbing attention. |
celebration |
A playful multi-tap flourish — level up, achievement unlocked. |
typewriter |
A very quick, light tick — keystroke feedback for a custom keyboard. |
drumRoll |
Four rapid light taps finishing on a slightly stronger accent. |
knock |
Three firm taps, like knocking on a door. |
pop |
A quick, sharp single pulse, like a bubble popping. |
bounce |
Strong, then two lighter echoes fading out. |
Developing on desktop
Windows/macOS/Linux have no vibration hardware, and flutter test runs
as a desktop process too — so haptic_patterns never auto-detects the
platform and starts making noise on its own. If you want audible
feedback while developing on desktop, opt in explicitly:
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:haptic_patterns/haptic_patterns.dart';
import 'package:haptic_patterns/haptic_patterns_platform_interface.dart';
void main() {
if (!kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux)) {
HapticPatternsPlatform.instance = DesktopHapticSimulator();
}
runApp(MyApp());
}
DesktopHapticSimulator plays audible beeps in place of real haptics —
sharpness maps to pitch, intensity scales duration — so distinct
patterns still sound distinct from each other. It's a rough
approximation for development feedback, not a faithful audio
reproduction.
Testing your own code
HapticPatternsPlatform.instance is swappable, so code that calls
.play() is straightforward to test without touching a real device:
import 'package:haptic_patterns/haptic_patterns.dart';
import 'package:haptic_patterns/haptic_patterns_platform_interface.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
class FakeHapticPlatform with MockPlatformInterfaceMixin implements HapticPatternsPlatform {
List<HapticInstruction>? lastPlayed;
@override
Future<void> playInstructions(List<HapticInstruction> instructions) async {
lastPlayed = instructions;
}
@override
Future<void> stop() async {}
@override
Future<bool> isSupported() async => true;
}
// In a test:
HapticPatternsPlatform.instance = FakeHapticPlatform();
Example app
The example app has three tabs that exercise the whole
public API: an on-screen keyboard bound to HapticFeedbackWidget, a
playground that plays every preset plus a custom event built from live
sliders, and a tap-the-target game that picks its pattern from runtime
state.
cd example
flutter run
Development
git clone https://github.com/modexanderson/haptic_patterns.git
cd haptic_patterns
flutter pub get
flutter test
License
MIT License — see LICENSE for details.
Libraries
- haptic_patterns
- Composable haptic feedback patterns for Flutter -- chain, repeat, and compose taps, buzzes, and custom sequences on Android and iOS.
- haptic_patterns_method_channel
- haptic_patterns_platform_interface