# How to Implement Momentum-Based Swipe Dismissal with Velocity

> Learn to implement momentum based swipe dismissal with velocity. Calculate velocity and trigger dismissal using drag distance and elapsed time for smooth UX.

- Repository: [Emil Kowalski/skills](https://github.com/emilkowalski/skills)
- Tags: how-to-guide
- Published: 2026-07-30

---

**To implement momentum-based swipe dismissal, calculate velocity by dividing the absolute drag distance by elapsed time in milliseconds, then trigger dismissal when velocity exceeds 0.11 px/ms or a safety distance threshold is crossed.**

The `emilkowalski/skills` repository defines specific standards for gesture-based interactions in the *Emil Design Engineering* skill files. Unlike fixed-distance swipe detection, momentum-based dismissal measures the speed of the user's gesture to create a physics-inspired throwing motion that feels natural across different screen sizes.

## The Velocity Formula

The core calculation for momentum-based dismissal is straightforward but requires precise timing. According to the implementation details in [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) (lines 441-452), velocity is calculated as:

```javascript
const velocity = Math.abs(dragDistance) / elapsedMs; // px/ms

```

This formula measures how many pixels the user moved per millisecond. The `dragDistance` represents the total horizontal or vertical displacement from the gesture start, while `elapsedMs` captures the total time between pointer down and pointer up events.

## Step-by-Step Implementation

### Record Start Position and Time

When the user begins the drag gesture, capture both the initial coordinate and a timestamp. This establishes the baseline for all subsequent calculations.

```javascript
const handlePointerDown = (e) => {
  startX.current = e.clientX;
  startTime.current = Date.now();
};

```

### Track Movement Interactively

While the user drags, update the visual position of the element in real-time. However, the critical velocity calculation happens only at the end of the gesture when the pointer releases.

### Calculate Velocity on Release

When the drag ends, compute the total distance traveled and the elapsed duration. The absolute value ensures direction-agnostic speed measurement.

```javascript
const handlePointerUp = (e) => {
  const distance = e.clientX - startX.current;
  const elapsed = Date.now() - startTime.current;
  const velocity = Math.abs(distance) / elapsed;
  
  // Continue with threshold check...
};

```

### Apply Threshold Logic

The dismissal logic combines velocity with an optional distance fallback for robustness. As documented in the standards, use a velocity threshold of **0.11 px/ms** (pixels per millisecond). You may also include a secondary distance threshold (typically 100px) to ensure slow but deliberate drags still dismiss.

```javascript
const SWIPE_THRESHOLD = 100; // px
const VELOCITY_THRESHOLD = 0.11; // px/ms

if (Math.abs(distance) >= SWIPE_THRESHOLD || velocity > VELOCITY_THRESHOLD) {
  onDismiss(); // Trigger dismissal animation
}

```

## React Implementation Examples

### Minimal Hook Approach

This standalone React hook implements the momentum calculation without external animation libraries:

```jsx
import { useRef } from "react";

function SwipeDismiss({ onDismiss }) {
  const startX = useRef(0);
  const startTime = useRef(0);
  const SWIPE_THRESHOLD = 100;
  const VELOCITY_THRESHOLD = 0.11;

  const handlePointerDown = (e) => {
    startX.current = e.clientX;
    startTime.current = Date.now();
  };

  const handlePointerUp = (e) => {
    const distance = e.clientX - startX.current;
    const elapsed = Date.now() - startTime.current;
    const velocity = Math.abs(distance) / elapsed;

    if (Math.abs(distance) >= SWIPE_THRESHOLD || velocity > VELOCITY_THRESHOLD) {
      onDismiss();
    }
  };

  return (
    <div
      onPointerDown={handlePointerDown}
      onPointerUp={handlePointerUp}
      style={{ touchAction: "none", userSelect: "none" }}
    >
      {/* swipe-able content */}
    </div>
  );
}

```

### Framer Motion with Springs

For applications using **Framer Motion**, combine velocity detection with spring physics to create interruptible animations that preserve momentum. The [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) (lines 61-73) specifically recommends springs for gesture-based dismissal because they naturally handle velocity carryover when interrupted.

```tsx
import { motion, useSpring } from "framer-motion";
import { useRef } from "react";

export default function Swipeable({ onDismiss }) {
  const startX = useRef(0);
  const startTime = useRef(0);
  const VELOCITY_THRESHOLD = 0.11;

  const spring = useSpring(0, { 
    type: "spring", 
    bounce: 0.2, 
    duration: 0.5 
  });

  const handleDragStart = (info) => {
    startX.current = info.point.x;
    startTime.current = Date.now();
  };

  const handleDragEnd = (info) => {
    const distance = info.point.x - startX.current;
    const elapsed = Date.now() - startTime.current;
    const velocity = Math.abs(distance) / elapsed;

    if (velocity > VELOCITY_THRESHOLD) {
      onDismiss();
    } else {
      // Snap back with spring physics
      spring.set(0);
    }
  };

  return (
    <motion.div
      drag="x"
      onDragStart={(_, info) => handleDragStart(info)}
      onDragEnd={(_, info) => handleDragEnd(info)}
      style={{ x: spring }}
    >
      {/* content */}
    </motion.div>
  );
}

```

## Why Springs Matter for Momentum

The animation standards in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) (lines 37-44) mandate spring-based animations for momentum gestures rather than ease-out curves. **Springs preserve velocity when interrupted**, meaning if the user grabs the element mid-animation, the motion continues naturally from the current speed rather than resetting.

This physicality creates a consistent experience where quick flicks dismiss elements regardless of screen size, while slow drags require crossing the distance threshold. The bounce and damping parameters (typically `bounce: 0.2` to `0.3`) provide the tactile resistance that users expect from real-world objects.

## Summary

- **Velocity calculation**: Divide absolute drag distance by elapsed milliseconds to get px/ms speed
- **Critical threshold**: Trigger dismissal when velocity exceeds **0.11 px/ms**
- **Safety fallback**: Combine with a distance threshold (e.g., 100px) for deliberate slow swipes
- **Animation type**: Use spring physics from [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) to maintain momentum during interrupts
- **Source files**: Reference [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) for the velocity formula and [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) for animation standards

## Frequently Asked Questions

### What is the exact velocity threshold for dismissal?

According to the `emilkowalski/skills` repository documentation, the dismissal threshold is **0.11 pixels per millisecond** (px/ms). This value is derived from empirical testing to distinguish between casual scrolling and intentional throw gestures.

### Should I use velocity alone or combine it with distance checks?

While velocity is the primary metric for momentum-based dismissal, the standards recommend combining it with an optional distance threshold (typically 100px). This ensures that slow, deliberate drags across sufficient distance still trigger dismissal even if they don't generate high velocity.

### Why are springs preferred over ease-out animations for swipe dismissal?

Springs are preferred because they preserve the element's velocity when interrupted. If a user catches the element mid-flight, the spring continues from that velocity naturally, whereas ease-out animations would create a discontinuity. This behavior is documented in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) under the Physicality and Springs section.

### How do I handle direction-specific dismissal (left vs right)?

Check the sign of `dragDistance` before taking the absolute value for velocity calculation. A positive distance indicates rightward movement, while negative indicates leftward. Apply the velocity threshold to the absolute value, then use the original signed distance to determine the dismissal direction and animation target.