Drag to dismiss
useDragDismissBuild finished
Flick it either way. Velocity dismisses it, not distance.
Distance OR velocity. Threshold-only dismissal is why so many cards feel stuck.
v2.4 — sheet snapping is in
Nine React hooks for drag, scroll and press. Interruptible springs, ease-out on everything that enters, nothing over 300 ms, and reduced-motion handled before you ask. 9 kB for the whole set.
$ npm i ripplekitReact 18+ · zero peer deps · MIT
Not a video. These are the shipped hooks, on this page, at their default settings.
useDragDismissBuild finished
Flick it either way. Velocity dismisses it, not distance.
Distance OR velocity. Threshold-only dismissal is why so many cards feel stuck.
useScrollProgressThe value is sprung, so it trails your scroll by a few frames instead of snapping to it.
useMagneticSpring interpolation, not a raw mouse offset. Raw offsets have no momentum and read as artificial.
Each one is independently importable — import two, ship two. The diagram on each card is the decision the hook makes for you.
useDragDismiss1.4 kBSwipe a card away. Reads velocity, so a flick counts even when the distance does not.
useScrollProgress0.9 kBElement progress through the viewport, spring-smoothed. Reads off a passive scroll listener.
useMagnetic0.8 kBPointer attraction with spring falloff. Ignores touch and coarse pointers on its own.
useSheet2.1 kBSnap points, damping past the last one, and a drag that hands off to momentum instead of stopping dead.
usePressScale0.4 kBscale(0.97) on press, 160 ms, correct curve. Four hundred bytes to stop shipping dead buttons.
useInterruptible1.2 kBRetargets a running animation from its current velocity rather than restarting it from zero.
By hand
const [x, setX] = useState(0);
const start = useRef(0);
const t0 = useRef(0);
onPointerDown → capture pointer, store x + time
onPointerMove → setX(e.clientX - start.current)
onPointerUp → const dt = Date.now() - t0.current;
const v = Math.abs(x) / dt;
if (Math.abs(x) > 90 || v > 0.11) dismiss();
else animate back — but from what velocity?
// then: multi-touch guard, pointer capture on leave,
// damping past the edge, reduced motion, and the
// spring that has to retarget mid-flight instead of
// restarting from zero.With Ripple Kit
import { useDragDismiss } from "ripplekit";
const card = useDragDismiss({
onDismiss: () => remove(id),
});
return <div {...card} className="card" />;Same behaviour, including the parts you would have found in review: the flick threshold, the boundary damping, the pointer capture, and an exit that stays faster than the entrance.
One install, nine hooks, and the defaults are the part you were going to get wrong.
$ npm i ripplekit