Flutter package
haptic_kit
Haptic feedback, vibration and animated widgets for Flutter — the right haptic at the right moment.
API reference
The full public API: Haptics, Vibration, HapticPattern, presets, capability detection, exceptions and widget signatures.
source: README.mdAPI
Library exports
A single import brings in everything: six primitive classes, three enums, a sealed exception hierarchy, two amplitude constants and nine animated widgets. Signatures below are taken verbatim from the package source.
import 'package:haptic_kit/haptic_kit.dart';| Symbol | Kind | Summary |
|---|---|---|
| Haptics | class | Short, semantic taps: impact, notification, selection, prepare. |
| Vibration | class | Longer vibrations: one-shot, custom waveforms, predefined effects, cancel. |
| HapticPattern | class | Fluent builder for Core Haptics / amplitude event sequences. |
| HapticEvent | class | A single event (intensity + sharpness) inside a HapticPattern. |
| VibrationPatterns | class | Ready-made presets — heartbeat, alarm, success, failure, … |
| HapticCapabilities | class | Runtime snapshot of what the current device can do. |
| HapticImpactStyle | enum | Impact tap styles: light, medium, heavy, soft, rigid. |
| HapticNotificationStyle | enum | Notification styles: success, warning, error. |
| PredefinedEffect | enum | OS predefined effects: tick, click, doubleClick, heavyClick. |
| VibrationException | sealed class | Base class for every error thrown by the package. |
| UnsupportedHapticException | class | Device cannot render the requested capability. |
| InvalidVibrationArgumentException | class | A parameter is out of range or invalid. |
| PlatformVibrationException | class | Native call failed or the plugin is not registered. |
| kMaxAmplitude | const int | Maximum amplitude value (255). |
| kMinAmplitude | const int | Minimum perceptible amplitude (1). |
| HapticBounce | widget | Tap → squash → recoil → elastic settle with impact haptics. |
| HapticPulse | widget | Looping breathing scale pulse that ticks on every beat. |
| HapticRating | widget | Star rating with a cascading fill + a tick per star. |
| HapticShake | widget | Externally-triggered error wiggle. |
| HapticSlider | widget | Slider that ticks on each detent crossing. |
| HapticStepper | widget | −/+ counter with bouncing buttons. |
| HapticToggle | widget | Animated switch with a selection tick on flip. |
| PressAndHoldToConfirm | widget | Long-press with a progress ring + densifying ticks. |
| SlideToConfirm | widget | Drag the handle to the end to confirm. |
API
Haptics
Short, semantic haptic taps tied to UI events, exposed as static methods on Haptics. Named Haptics (not HapticFeedback) to avoid clashing with Flutter's own HapticFeedback from package:flutter/services.dart.
static Future<void> impact(HapticImpactStyle style)
static Future<void> notification(HapticNotificationStyle style)
static Future<void> selection()
static Future<bool> prepare()Returns: impact, notification and selection return Future<void>. prepare() returns Future<bool> — true when the platform actually pre-warmed something (iOS), false when the call is a no-op (Android).
await Haptics.impact(HapticImpactStyle.medium);
await Haptics.notification(HapticNotificationStyle.success);
await Haptics.selection();
// Optional: pre-warm the generators for the lowest-latency next tap.
final warmed = await Haptics.prepare(); // true on iOS, false on AndroidHapticImpactStyle
enum HapticImpactStyle {
light, // iOS: .light Android: EFFECT_TICK
medium, // iOS: .medium Android: EFFECT_CLICK
heavy, // iOS: .heavy Android: EFFECT_HEAVY_CLICK
soft, // iOS 13+, falls back to light
rigid, // iOS 13+, falls back to heavy
}HapticNotificationStyle
enum HapticNotificationStyle {
success, // Confirms a successful action
warning, // Warns about a non-fatal issue
error, // Signals a failure
}API
Vibration
Longer-form vibration on Vibration — one-shot durations, custom waveforms, predefined OS effects and cancellation. All methods are static.
/// Matches Android's VibrationEffect.MAX_AMPLITUDE.
const int kMaxAmplitude = 255;
/// Minimum amplitude that still produces a perceptible vibration.
const int kMinAmplitude = 1;Vibration.vibrate
Vibrate for duration. Amplitude is silently clamped on devices without amplitude control.
static Future<void> vibrate({
required Duration duration,
int? amplitude,
})| Parameter | Type | Req. | Default | Description |
|---|---|---|---|---|
| duration | Duration | Yes | — | How long to vibrate. Must be > 0 ms. |
| amplitude | int? | No | null | Strength in 1..255. Silently clamped on devices without amplitude control. null = device default. |
Returns: Future<void>.
Throws: InvalidVibrationArgumentException when duration is zero or negative, or when amplitude is set outside [1, 255].
Vibration.vibrateWaveform
Play a custom waveform. On iOS without Core Haptics support the call falls back to a sequence of system vibrations approximating timings.
static Future<void> vibrateWaveform({
required List<Duration> timings,
List<int>? amplitudes,
int repeat = -1,
})| Parameter | Type | Req. | Default | Description |
|---|---|---|---|---|
| timings | List<Duration> | Yes | — | Off/on durations, starting with an off period (Android createWaveform convention). Must not be empty. |
| amplitudes | List<int>? | No | null | Same length as timings, values in [0, 255] (0 = pause). null = default strength. |
| repeat | int | No | -1 | Index in timings to loop from, or -1 for no loop. |
Returns: Future<void>.
Throws: InvalidVibrationArgumentException when timings is empty, when amplitudes length differs from timings or holds a value outside [0, 255], or when repeat is neither -1 nor a valid index.
// Three pulses with growing amplitude.
await Vibration.vibrateWaveform(
timings: const [
Duration.zero,
Duration(milliseconds: 100),
Duration(milliseconds: 100),
Duration(milliseconds: 100),
Duration(milliseconds: 100),
Duration(milliseconds: 100),
],
amplitudes: const [0, 80, 0, 160, 0, 255],
);Vibration.playPredefined
Trigger one of the platform's predefined effects. Android API 29+ uses VibrationEffect.createPredefined; older Android and iOS translate to the closest equivalent.
static Future<void> playPredefined(PredefinedEffect effect)Returns: Future<void>.
enum PredefinedEffect {
tick, // Android: EFFECT_TICK
click, // Android: EFFECT_CLICK
doubleClick, // Android: EFFECT_DOUBLE_CLICK
heavyClick, // Android: EFFECT_HEAVY_CLICK
}Vibration.cancel
Stop any vibration started by this plugin.
static Future<void> cancel()Returns: Future<void>.
API
HapticPattern builder
A fluent builder for custom event sequences — CHHapticPattern on iOS, a createWaveform on Android where intensity maps to amplitude and sharpness is ignored. Each builder instance holds its own state and is single-use.
HapticEvent
A single event in a pattern.
const HapticEvent({
required double intensity, // 0.0..1.0 — overall strength
required double sharpness, // 0.0..1.0 — perceptual sharpness (iOS only)
Duration duration = Duration.zero, // non-zero = continuous event
Duration relativeTime = Duration.zero, // offset from pattern start
})| Parameter | Type | Req. | Default | Description |
|---|---|---|---|---|
| intensity | double | Yes | — | 0.0–1.0 — overall strength of the event. |
| sharpness | double | Yes | — | 0.0–1.0 — perceptual sharpness (iOS only; ignored on Android, which has no equivalent). |
| duration | Duration | No | Duration.zero | If non-zero, the event is continuous for this duration; otherwise it is a single tap. |
| relativeTime | Duration | No | Duration.zero | Offset from pattern start at which this event begins. |
Throws: An assertion error when intensity or sharpness is outside [0, 1].
HapticPattern.builder
Start a new pattern, then chain tap, continuous, pause and addEvent before calling play.
factory HapticPattern.builder()
HapticPattern tap({
required double intensity,
required double sharpness,
})
HapticPattern continuous({
required Duration duration,
required double intensity,
required double sharpness,
})
HapticPattern pause(Duration duration)
HapticPattern addEvent(HapticEvent event)
int get length // Number of events queued so far.
List<HapticEvent> get events // Snapshot of the current event list.
Future<void> play()Returns: tap, continuous, pause and addEvent return the same HapticPattern for chaining; play() returns Future<void>.
Throws: InvalidVibrationArgumentException from continuous/pause when the duration is <= 0 ms, and from play when the pattern has no events. play may also throw UnsupportedHapticException on devices with neither Core Haptics nor a waveform-amplitude vibrator.
await HapticPattern.builder()
.tap(intensity: 0.4, sharpness: 0.6)
.pause(const Duration(milliseconds: 80))
.tap(intensity: 1.0, sharpness: 0.9)
.continuous(
duration: const Duration(milliseconds: 250),
intensity: 0.7,
sharpness: 0.3,
)
.play();API
VibrationPatterns presets
Ready-to-use waveforms and haptic patterns on VibrationPatterns. Each entry returns a Future<void> so you can await playback in UI callbacks.
static Future<void> heartbeat()
static Future<void> notification()
static Future<void> alarm({bool repeat = true})
static Future<void> tick()
static Future<void> doubleTap()
static Future<void> success()
static Future<void> failure()
static Future<void> chargeUp({
Duration duration = const Duration(milliseconds: 600),
})| Preset | What it feels like |
|---|---|
| heartbeat() | Two short pulses mimicking a heartbeat. |
| notification() | Three escalating pulses — typical incoming-notification pattern. |
| alarm({bool repeat = true}) | Long ringing pattern — alarms / incoming calls. Loops by default. |
| tick() | Single very short tick — a hair stronger than a selection tap. |
| doubleTap() | Two quick, equal taps — a double-tap acknowledgement. |
| success() | Confirmation: light tap → strong tap. |
| failure() | Failure / cancel: two strong taps. |
| chargeUp({Duration duration = const Duration(milliseconds: 600)}) | Long ramp from soft to strong — a “charging up” moment. |
API
Capability detection
Query what the current device can do before reaching for an advanced capability. Call HapticCapabilities.query() once on app start (or the first user interaction) and cache the result — the values do not change at runtime.
static Future<HapticCapabilities> query()Returns: Future<HapticCapabilities> — a snapshot with the following fields.
| Parameter | Type | Req. | Default | Description |
|---|---|---|---|---|
| hasVibrator | bool | Device has any kind of vibrator hardware. | ||
| hasAmplitudeControl | bool | Device can render variable-amplitude waveforms. Android: Vibrator.hasAmplitudeControl() (API 26+); iOS: true when Core Haptics is available. | ||
| supportsCustomPatterns | bool | Custom HapticPatterns with intensity + sharpness. Android: API 26+ with amplitude control; iOS: Core Haptics devices (iPhone 8+). | ||
| supportsPredefinedEffects | bool | VibrationEffect.createPredefined (Android API 29+). Always false on iOS — predefined calls map to the closest impact style instead. | ||
| supportsImpactFeedback | bool | UIImpactFeedbackGenerator-style taps. iOS: true on iOS 10+; Android: true on API 23+ even without an amplitude-controlled vibrator. |
final caps = await HapticCapabilities.query();
if (caps.supportsCustomPatterns) {
await VibrationPatterns.success();
} else {
await Haptics.notification(HapticNotificationStyle.success);
}API
Exceptions
Every error thrown by haptic_kit is a subclass of the sealed VibrationException, so an exhaustive switch can handle them all.
sealed class VibrationException implements Exception {
const VibrationException(this.message);
final String message;
}
class UnsupportedHapticException extends VibrationException {
const UnsupportedHapticException(super.message);
}
class InvalidVibrationArgumentException extends VibrationException {
const InvalidVibrationArgumentException(super.message);
}
class PlatformVibrationException extends VibrationException {
const PlatformVibrationException(super.message, {this.code});
final String? code;
}| Exception | Thrown when |
|---|---|
| InvalidVibrationArgumentException | A parameter is out of range (negative duration, amplitude > 255, mismatched lists, …). |
| UnsupportedHapticException | The device cannot render the requested capability. |
| PlatformVibrationException | The native side returned an error or the plugin is not registered. |
API
Animated widgets
Nine production-ready widgets that pair an animation with the matching haptic. Constructor signatures are shown below; for the full parameter tables, defaults and examples see the Animated widgets page. Widgets exposing a State class are driven externally via a GlobalKey.
HapticBounce
Tap wrapper that squashes, recoils past its resting size and settles with an elastic wobble. Fires impact(light) on press-down and medium on release.
const HapticBounce({
super.key,
required this.child,
this.onTap,
this.bounceOnRelease = true,
this.pressedScale = 0.92,
this.overshootScale = 1.12,
this.pressDuration = const Duration(milliseconds: 110),
this.releaseDuration = const Duration(milliseconds: 480),
this.haptics = true,
this.behavior = HitTestBehavior.opaque,
})PressAndHoldToConfirm
Hold to confirm with a progress ring and a 12-tick schedule that escalates selection → light → medium → heavy.
const PressAndHoldToConfirm({
super.key,
required this.child,
required this.onConfirm,
this.holdDuration = const Duration(seconds: 2),
this.ringSize = 96,
this.ringStrokeWidth = 6,
this.ringColor,
this.showRingAtFingerPosition = true,
})State (via GlobalKey<PressAndHoldToConfirmState>): void reset() re-arms the widget after a confirmation.
HapticToggle
Custom-drawn switch with a spring-back animation and a selection tick on flip. Controlled — the caller owns value.
const HapticToggle({
super.key,
required this.value,
required this.onChanged,
this.activeColor,
this.inactiveColor = const Color(0xFF3A3A3C),
this.thumbColor = Colors.white,
this.width = 56,
this.height = 32,
this.duration = const Duration(milliseconds: 220),
})HapticSlider
Slider that fires a haptic tick whenever the value crosses a discrete detent — like the iOS picker wheel.
const HapticSlider({
super.key,
required this.value,
required this.onChanged,
this.min = 0.0,
this.max = 1.0,
this.divisions,
this.tickEvery,
this.label,
this.activeColor,
this.tickStyle = HapticImpactStyle.light,
this.endTickStyle = HapticImpactStyle.medium,
})HapticStepper
−/+ counter with bouncing buttons and a number that slides. Each press fires light; hitting min/max fires heavy.
const HapticStepper({
super.key,
required this.value,
required this.onChanged,
this.min = 0,
this.max = 99,
this.step = 1,
})HapticShake
Externally-triggered horizontal wiggle + notification(error) — the classic “wrong password” shake.
const HapticShake({
super.key,
required this.child,
this.amplitude = 10.0,
this.duration = const Duration(milliseconds: 420),
this.haptics = true,
})State (via GlobalKey<HapticShakeState>): Future<void> shake() plays the shake once (restarts if already shaking).
HapticPulse
Looping “breathing” scale pulse that fires a haptic tap on every beat — the attention-getter counterpart to HapticShake.
const HapticPulse({
super.key,
required this.child,
this.minScale = 1.0,
this.maxScale = 1.08,
this.period = const Duration(milliseconds: 600),
this.autoPlay = true,
this.pulseCount,
this.impactStyle = HapticImpactStyle.light,
this.haptics = true,
})State (via GlobalKey<HapticPulseState>): bool get isPulsing, void start(), void stop().
SlideToConfirm
Drag the handle from edge to edge to confirm, with light ticks at 25% / 50% / 75% and a heavy impact at 100%.
const SlideToConfirm({
super.key,
required this.onConfirmed,
this.label = 'Slide to confirm',
this.height = 64,
this.handleIcon = Icons.arrow_forward,
this.confirmedIcon = Icons.check,
this.trackColor,
this.handleColor,
this.textColor,
})State (via GlobalKey<SlideToConfirmState>): void reset() re-arms the widget after a confirmation.
HapticRating
Star rating that fills with a cascading animation, firing one selection tick per star as it lights up.
const HapticRating({
super.key,
required this.value,
required this.onChanged,
this.starCount = 5,
this.size = 36,
this.spacing = 6,
this.activeColor = Colors.amber,
this.inactiveColor = const Color(0xFF3A3A3C),
this.cascadeDelay = const Duration(milliseconds: 65),
})Let's make something together.
If you have any questions about a new project or other inquiries, feel free to contact us. We will get back to you as soon as possible.
