# How to Calculate Velocity for Momentum-Based Swipe Dismissal

> Learn how to calculate velocity for momentum-based swipe dismissal. Divide drag distance by elapsed time to exceed 0.11 px/ms and trigger dismissal.

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

---

**Calculate velocity by dividing the absolute drag distance by the elapsed time in milliseconds, then trigger dismissal if the result exceeds 0.11 px/ms.**

Momentum-based swipe dismissal creates natural, physics-driven interactions by measuring flick speed rather than relying on arbitrary distance thresholds. In the `emilkowalski/skills` repository, this pattern is codified in the design engineering standards to ensure consistent gesture handling across components. Mastering how to calculate velocity for momentum-based swipe dismissal allows you to implement interruptible animations that respond to user intent rather than fixed coordinates.

## The Velocity Formula

According to [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) (lines 441-452), the core calculation relies on basic kinematics:

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

```

Where:
- **`dragDistance`** is the total pixels moved from pointer start to end (positive or negative depending on direction)
- **`elapsedMs`** is the time delta between `pointerdown` and `pointerup` events
- **`Math.abs()`** ensures velocity is always a positive scalar value regardless of swipe direction

The repository specifies **0.11 px/ms** as the dismissal threshold. When velocity exceeds this value, the gesture carries enough momentum to warrant dismissal, even if the user hasn't dragged the element past a fixed distance threshold.

## Implementation Steps

Follow this exact sequence used throughout the `emilkowalski/skills` standards:

1. **Record the drag start time** when the user begins the gesture (store `Date.now()` or `performance.now()`).
2. **Track the drag distance** continuously while the pointer moves, updating the delta from the original coordinates.
3. **Capture the end state** on pointer release, computing `elapsedMs` as the current time minus the start time.
4. **Compute velocity** using the formula above.
5. **Evaluate against thresholds**—if `velocity > 0.11` **or** `Math.abs(dragDistance) >= SWIPE_THRESHOLD`, execute the dismiss animation.

## Code Implementation Examples

### Vanilla React Hook

This minimal implementation follows the pattern described in the source files, using React refs to avoid re-renders during the gesture:

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

function SwipeDismiss({ onDismiss, children }) {
  const startX = useRef(0);
  const startTime = useRef(0);
  const SWIPE_THRESHOLD = 100; // px, safety fallback
  const VELOCITY_THRESHOLD = 0.11; // px/ms from skills/emil-design-eng

  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 (velocity > VELOCITY_THRESHOLD || Math.abs(distance) >= SWIPE_THRESHOLD) {
      onDismiss(); // Trigger spring-based exit animation
    }
  };

  return (
    <div
      onPointerDown={handlePointerDown}
      onPointerUp={handlePointerUp}
      style={{ touchAction: "none", userSelect: "none" }}
    >
      {children}
    </div>
  );
}

```

### Framer Motion with Spring Physics

As documented in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) (lines 61-73), springs preserve velocity when interrupted, making them ideal for momentum gestures. This example combines the velocity calculation with Framer Motion's declarative API:

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

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

  const spring = useSpring(0, {
    stiffness: 300,
    damping: 30
  });

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

  const handleDragEnd = (event, 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 that respects current momentum
      spring.set(0);
    }
  };

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

```

## Source Files and Standards

The velocity calculation and dismissal logic are defined across these specific files:

- **[`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md)** (lines 441-452): Contains the exact velocity formula (`Math.abs(dragDistance) / timeTaken`) and the 0.11 px/ms threshold specification for momentum-based dismissal.
- **[`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md)** (lines 61-73): Documents why springs are the preferred animation primitive for preserving velocity during interruptible gestures.
- **[`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md)** (lines 37-44): Lists the velocity-based dismissal rule alongside damping and bounce recommendations for drag interactions.

## Summary

- **Velocity equals absolute distance divided by elapsed milliseconds**, producing a value in px/ms.
- **The dismissal threshold is 0.11 px/ms** according to the `emilkowalski/skills` standards—any swipe faster than this should trigger dismissal.
- **Combine velocity with a distance fallback** (e.g., 100px) to handle slow, deliberate drags that still cross the element's midpoint.
- **Use spring animations** to preserve momentum when the gesture ends, allowing the UI to continue moving naturally before settling or dismissing.

## Frequently Asked Questions

### What units should I use for the velocity calculation?

The `emilkowalski/skills` repository specifies **pixels per millisecond (px/ms)**. This produces small decimal values (like 0.11) that are easy to reason about and compare against thresholds. Always measure time in milliseconds, not seconds, to avoid floating-point confusion when dividing small distances by large time values.

### Why use 0.11 px/ms as the threshold instead of a fixed distance?

A fixed distance threshold forces users to drag an element a specific number of pixels regardless of their intent. The **0.11 px/ms threshold** (as defined in [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md)) recognizes that a quick flick carries momentum and should dismiss the element even on small screens, while a slow drag that travels far might indicate hesitation rather than intent to dismiss.

### How do I handle interruptions during the swipe?

According to [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) (lines 61-73), you should use **spring-based animations** rather than time-based tweens. Springs naturally preserve velocity when interrupted, meaning if the user grabs the element mid-flight, the animation continues from the current velocity rather than resetting to zero.

### Should I check velocity or distance first?

Check both simultaneously using an OR condition. The repository recommends using velocity as the primary signal but maintaining an optional distance fallback (`Math.abs(swipeAmount) >= SWIPE_THRESHOLD`) for robustness. This handles edge cases where very slow drags eventually cross a significant portion of the screen, indicating clear user intent despite low velocity.