Open Source

Flutter package

haptic_kit

Haptic feedback, vibration and animated widgets for Flutter — the right haptic at the right moment.

v2.1.223.3k / month32 likesMITAndroid · iOS9 animated widgets

Animated widgets

Nine production-ready widgets that pair each animation with the correct haptic automatically.

source: README.md

Widget

HapticBounce

Wraps any child so a tap squashes it, recoils past its resting size and settles with an elastic wobble — a 3-segment TweenSequence with weights 1 : 2 : 3 (squash easeIn, recoil easeOutCubic, settle elasticOut). Fires Haptics.impact(light) on press-down and medium on release.

HapticBouncedart
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,
})
ParamTypeDefaultMeaning
childWidgetrequiredThe widget to scale on press.
onTapVoidCallback?Equivalent to GestureDetector.onTap.
bounceOnReleasebooltruetrue: recoil + elastic-settle on release; false: symmetric return, no overshoot.
pressedScaledouble0.92Scale at the bottom of the press; must be in (0, 1).
overshootScaledouble1.12Peak scale during recoil; must be >= 1.0; ignored when bounceOnRelease is false.
pressDurationDuration110 msPress-down animation duration.
releaseDurationDuration480 msRelease animation (recoil + elastic settle).
hapticsbooltrueFire Haptics.impact(light) on press-down and medium on release.
behaviorHitTestBehavioropaquePassed to the underlying GestureDetector.

Constraint violations throw an ArgumentError at construction time, in both debug and release.

Exampledart
HapticBounce(
  onTap: () => doSomething(),
  child: Container(
    padding: const EdgeInsets.all(24),
    decoration: const BoxDecoration(/* ... */),
    child: const Text('Press me'),
  ),
)

Widget

PressAndHoldToConfirm

Hold-to-confirm with a progress ring and a densifying tick schedule. A single AnimationController drives the ring, the haptic schedule and the callback, so there are no race conditions. The 12 ticks escalate selectionlightmediumheavy and seal with a final heavy impact at completion.

PressAndHoldToConfirmdart
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,
})
ParamTypeDefaultMeaning
childWidgetrequiredThe widget rendered under the finger.
onConfirmVoidCallbackrequiredFired exactly once when the press completes; use reset() to allow another confirmation.
holdDurationDuration2 sHow long the user must hold.
ringSizedouble96Diameter of the progress ring in logical pixels.
ringStrokeWidthdouble6Stroke width of the ring.
ringColorColor?Theme.colorScheme.primaryRing colour.
showRingAtFingerPositionbooltruetrue: ring follows the finger; false: centred over the child.

The state method void reset() on PressAndHoldToConfirmState re-arms the widget after a successful confirmation. It has no effect while a press is in progress or before the first confirmation.

Exampledart
final key = GlobalKey<PressAndHoldToConfirmState>();

PressAndHoldToConfirm(
  key: key,
  holdDuration: const Duration(seconds: 2),
  onConfirm: () => unbox(),
  child: const SizedBox(
    height: 240,
    child: Center(child: Icon(Icons.card_giftcard, size: 96)),
  ),
)

// Re-arm for another confirmation later:
key.currentState?.reset();

Widget

HapticToggle

A custom-drawn pill + thumb with a spring-back animation on every change. It behaves like a Switch (controlled — the caller owns value and handles changes through onChanged), fires Haptics.selection() on toggle, and slides with Curves.easeOutBack.

HapticToggledart
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),
})
ParamTypeDefault
valueboolrequired
onChangedValueChanged<bool>?required
activeColorColor?Theme.colorScheme.primary
inactiveColorColorColor(0xFF3A3A3C)
thumbColorColorColors.white
widthdouble56
heightdouble32
durationDuration220 ms
Exampledart
HapticToggle(
  value: _enabled,
  onChanged: (v) => setState(() => _enabled = v),
)

Widget

HapticSlider

A slider that fires a haptic tick whenever the value crosses a discrete detent — like the iOS picker wheel. It caches the last detent index to detect crossings; intermediate ticks and the min/max endpoints get different impact styles.

HapticSliderdart
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,
})
ParamTypeDefaultMeaning
valuedoublerequired
onChangedValueChanged<double>required
min / maxdouble0.0 / 1.0
divisionsint?Discrete divisions; if both are set, divisions wins; must be > 0.
tickEverydouble?Distance between ticks in value units (used when divisions is null); must be > 0.
labelString?Passed to Slider.
activeColorColor?Passed to Slider.
tickStyleHapticImpactStylelightHaptic style for intermediate ticks.
endTickStyleHapticImpactStylemediumHaptic style for the min and max endpoints.
Exampledart
HapticSlider(
  value: _v,
  min: 0,
  max: 100,
  divisions: 10,                              // tick every 10 units
  onChanged: (v) => setState(() => _v = v),
)

Widget

HapticStepper

A numeric stepper with bouncing −/+ buttons and a number that slides up (on increment) or down (on decrement). Each button press fires a light haptic; hitting min or max fires a heavy haptic to signal the boundary. It composes HapticBounce buttons with an AnimatedSwitcher.

HapticStepperdart
const HapticStepper({
  super.key,
  required this.value,
  required this.onChanged,
  this.min = 0,
  this.max = 99,
  this.step = 1,
})
ParamTypeDefaultConstraint
valueintrequired
onChangedValueChanged<int>required
minint0min < max (assert)
maxint99
stepint1step > 0 (assert)
Exampledart
HapticStepper(
  value: _count,
  min: 0,
  max: 99,
  onChanged: (v) => setState(() => _count = v),
)

Widget

HapticShake

Wiggles its child with a decaying horizontal shake and fires an error notification — the classic "wrong password" motion. It is triggered externally through a GlobalKey: a 6-segment TweenSequence of decreasing-amplitude translations ending at zero.

HapticShakedart
const HapticShake({
  super.key,
  required this.child,
  this.amplitude = 10.0,
  this.duration = const Duration(milliseconds: 420),
  this.haptics = true,
})
ParamTypeDefaultMeaning
childWidgetrequired
amplitudedouble10.0Maximum horizontal displacement in logical pixels. Subsequent swings decay from this value.
durationDuration420 msTotal duration of the shake.
hapticsbooltrueFire Haptics.notification(error) at the start of the shake.

The state method Future<void> shake() on HapticShakeState plays the shake animation once; calling it while already shaking restarts from the beginning.

Exampledart
final shakeKey = GlobalKey<HapticShakeState>();

HapticShake(key: shakeKey, child: TextField(/* ... */));

// On validation failure:
shakeKey.currentState?.shake();

Widget

SlideToConfirm

Drag a handle to the end to confirm — the iconic pattern in payment, driver and "are you sure" flows. Light ticks fire at 25%, 50% and 75% progress; reaching 100% fires a heavy impact + onConfirmed and the handle stays at the end. Releasing before 100% springs the handle back with a light tick.

SlideToConfirmdart
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,
})
ParamTypeDefault
onConfirmedVoidCallbackrequired
labelString'Slide to confirm'
heightdouble64
handleIconIconDataIcons.arrow_forward
confirmedIconIconDataIcons.check
trackColor / handleColor / textColorColor?theme-derived

The state method void reset() on SlideToConfirmState re-arms the widget after a successful confirmation.

Exampledart
SlideToConfirm(
  label: 'Slide to pay',
  onConfirmed: () => pay(),
)

Widget

HapticRating

A star rating that cascades on tap: tapping the 4th star lights up four stars in sequence, firing one selection tick per star. The cascade is driven by a Timer.periodic with a 65 ms delay between stars.

HapticRatingdart
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),
})
ParamTypeDefaultMeaning
valueintrequiredCurrent rating, in 0..starCount.
onChangedValueChanged<int>required
starCountint5Must be > 0 (assert).
sizedouble36Star icon size.
spacingdouble6Gap between stars.
activeColorColorColors.amber
inactiveColorColorColor(0xFF3A3A3C)
cascadeDelayDuration65 msTime between each star lighting up during the cascade.
Exampledart
HapticRating(
  value: _rating,
  starCount: 5,
  onChanged: (v) => setState(() => _rating = v),
)

Widget

HapticPulse

A repeating scale pulse (heartbeat) around its child, with a light haptic tick on each peak. Pulses forever by default or a fixed number of times via pulseCount; drive it manually with the state's start() / stop() and read isPulsing.

HapticPulsedart
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,
})
ParamTypeDefaultMeaning
childWidgetrequiredThe widget to pulse.
minScaledouble1.0Scale at the trough of each pulse (must be > 0).
maxScaledouble1.08Scale at the peak of each pulse (must be > 0).
periodDuration600 msLength of one full pulse cycle.
autoPlaybooltrueStart pulsing as soon as the widget mounts.
pulseCountint?nullNumber of pulses; null pulses infinitely (must be > 0 when set).
impactStyleHapticImpactStylelightHaptic fired at each pulse peak.
hapticsbooltrueSet false to animate silently.
Exampledart
HapticPulse(
  maxScale: 1.12,
  period: const Duration(milliseconds: 500),
  pulseCount: 3, // null = pulse forever
  impactStyle: HapticImpactStyle.light,
  child: const Icon(Icons.favorite, size: 48),
)

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.

Codigee
We are using cookies. Learn more