Open Source

Flutter package

haptic_kit

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

v2.2.019.6k / 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