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:
Apple-Style Config (Recommended Default)
{ 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
velocityoption to maintain continuity when gestures end. - Threshold at ~0.11 for momentum-based dismissals, snapping back below and completing above.
- Motion's
useSpringprovides thevelocityAPI 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →