How to Configure Spring Animations for Drag, Gesture, and Momentum Interactions

Use a spring with velocity carry-over for any gesture-driven UI—the spring retains release velocity for natural momentum and remains interruptible when users change direction mid-gesture.

The emilkowalski/skills repository establishes clear standards for motion design, with spring animations as the core technique for responsive, physical-feeling interfaces. Whether you're building draggable sheets, swipe-to-dismiss toasts, or momentum-based scrolling, spring configuration determines how convincing your interaction feels.

Why Springs Beat Transitions for Gesture Interactions

Spring physics excel where CSS transitions and keyframes fail. According to skills/review-animations/STANDARDS.md, the critical advantage is velocity carry-over: when a user releases a drag, the spring seeds from the release velocity instead of restarting from zero.

Key benefits for gesture-based interactions:

  • Interruptibility – Springs retarget from their current state. If a user reverses direction mid-gesture, the animation continues smoothly rather than snapping.
  • Natural momentum – Physical objects don't stop abruptly. Feeding release velocity to a spring creates that "thrown" sensation.
  • Continuous responsiveness – The spring reacts to every frame of input, maintaining connection between finger and UI element.

The repository notes that keyframes feel "brick-wall-like" because they ignore velocity state—springs solve this by design.

Canonical Spring Configurations

skills/review-animations/STANDARDS.md defines two recommended approaches:

{ type: "spring", duration: 0.5, bounce: 0.2 }

This produces a critically-damped spring—easy to reason about, predictable timing, subtle overshoot. Ideal for most UI work where you want polish without distraction.

Physics-Based Config (Precise Control)

{ type: "spring", mass: 1, stiffness: 100, damping: 10 }

Use when you need specific physical characteristics: heavier mass for sluggish, substantial feel; higher stiffness for snappier response; damping tuned to eliminate or emphasize oscillation.

Spring Setup by Interaction Type

Interaction Spring config Velocity handling
Drag-to-move (sheets, cards) { type: "spring", duration: 0.5, bounce: 0.2 } Pass pointer velocity on pointerup
Flick-to-dismiss (toasts, modals) Same, or increase bounce to 0.3 for playful exit Compute distance / elapsedMs, apply as velocity option if threshold exceeded
Continuous decorative motion { type: "spring", mass: 1, stiffness: 100, damping: 10 } No external velocity; natural settle creates subtle "alive" movement

The repository recommends a velocity threshold of approximately 0.11 (normalized) for momentum-based dismissals, as noted in the design guidelines.

Implementation with Motion (Framer Motion)

skills/pick-ui-library/SKILL.md recommends Motion (formerly Framer Motion) for React projects. The useSpring hook provides direct API access to velocity seeding.

Drag-to-Move Card

import { useSpring, motion } from "motion/react";

export function DraggableCard() {
  const [{ x, y }, api] = useSpring(
    { x: 0, y: 0 },
    { type: "spring", duration: 0.5, bounce: 0.2 }
  );

  const handleDrag = (e: PointerEvent) => {
    api.start({ x: e.clientX, y: e.clientY });
  };

  const handleRelease = (
    e: PointerEvent,
    info: { velocity: { x: number; y: number } }
  ) => {
    api.start({
      x: e.clientX,
      y: e.clientY,
      velocity: Math.hypot(info.velocity.x, info.velocity.y) / 1000,
    });
  };

  return (
    <motion.div
      style={{ x, y, touchAction: "none", cursor: "grab" }}
      onPointerDown={(e) =>
        e.currentTarget.setPointerCapture(e.pointerId)
      }
      onPointerMove={handleDrag}
      onPointerUp={handleRelease}
    >
      {/* Card content */}
    </motion.div>
  );
}

Momentum-Dismiss Toast

import { useSpring, motion } from "motion/react";

export function Toast({ onClose }: { onClose: () => void }) {
  const [{ y }, api] = useSpring(
    { y: 0 },
    { type: "spring", duration: 0.5, bounce: 0.2 }
  );

  const handleSwipe = (
    e: PointerEvent,
    info: { velocity: { y: number } }
  ) => {
    const velocity = Math.abs(info.velocity.y) / 1000;

    if (velocity > 0.11) {
      api.start({ y: 200, velocity }).then(onClose);
    } else {
      api.start({ y: 0 });
    }
  };

  return (
    <motion.div
      style={{
        y,
        touchAction: "none",
        background: "#333",
        color: "#fff",
        padding: "8px 16px",
      }}
      onPointerDown={(e) =>
        e.currentTarget.setPointerCapture(e.pointerId)
      }
      onPointerMove={handleSwipe}
    >
      Swipe up to dismiss
    </motion.div>
  );
}

Both examples demonstrate the pattern from skills/apple-design/SKILL.md: update spring target during drag, seed with computed velocity on release.

Velocity Calculation and Thresholds

For momentum interactions, compute velocity as:

const velocity = Math.abs(distance) / elapsedMs;

The repository suggests normalizing to approximately 0.11 as the threshold for triggering a dismiss action. Below this, the element should spring back to its original position; above it, the momentum carries through to completion.

Normalization (dividing by 1000 in the examples above) converts to units compatible with Motion's spring API.

Key Source Files

File Purpose
skills/review-animations/STANDARDS.md Canonical spring definitions and configuration standards
skills/apple-design/SKILL.md Design rationale for gesture-spring pairing, velocity mapping guidelines
skills/improve-animations/AUDIT.md Motion audit criteria emphasizing springs for interruptible gesture UI
skills/pick-ui-library/SKILL.md Library recommendations, including Motion for spring-based animation

Summary

  • Springs preserve velocity where transitions reset to zero, creating natural momentum for drag and gesture interactions.
  • Apple-style config (duration: 0.5, bounce: 0.2) handles most UI needs; use physics parameters for precise physical feel.
  • Seed release velocity into the spring via the velocity option to maintain continuity when gestures end.
  • Threshold at ~0.11 for momentum-based dismissals, snapping back below and completing above.
  • Motion's useSpring provides the velocity API needed to implement these patterns in React.

Frequently Asked Questions

What's the difference between duration/bounce and mass/stiffness/damping springs?

The duration/bounce shorthand produces the same underlying physics but with intuitive controls: duration sets settle time, bounce sets overshoot amount (0-1). The physics parameters expose the raw spring model for cases where you need specific mass or stiffness characteristics. skills/review-animations/STANDARDS.md recommends the shorthand as the default.

Why not use CSS transition with cubic-bezier for gestures?

CSS transitions restart from zero velocity on every target change, creating that "brick-wall" feel when gestures interrupt. Springs maintain state—their equations incorporate current velocity and position, enabling retargeting continuity essential for responsive drag interactions.

How do I prevent springs from feeling "bouncy" or unprofessional?

Keep bounce between 0.1 and 0.3 for UI realism, per skills/apple-design/SKILL.md. Higher values feel playful or toy-like; 0 produces overdamped motion with no overshoot. Critically-damped springs (bounce ~0.2) balance responsiveness with stability.

Which library does the repository recommend for spring animations?

Motion (formerly Framer Motion), as documented in skills/pick-ui-library/SKILL.md. Its useSpring hook exposes the velocity option required for momentum seeding, with React-native integration that matches the repository's implementation patterns.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →