How Swipe Gestures Actually Work
The Anatomy Of The Swipe is one of those things that looks effortless on screen but involves a surprisingly tangled stack of event listeners, coordinate math, and frame scheduling under the hood. People treat swipes as a given — you drag a card left, it flies off, the deck updates. What actually happens is a chain of decisions made roughly sixty times per second, and when any link in that chain misfires, you get the jittery, unresponsive garbage you see on half the apps that slap a swipe on without thinking about it. I've built and broken a lot of swipe interfaces across React Native, Flutter, and raw web, and the anatomy breaks down into five pieces that all need to talk to each other correctly. Start with the event pipeline. On the web this means pointer events — pointerdown, pointermove, pointerup. On native it's touch events that map to the same conceptual flow. The critical detail everyone misses is the distinction between a swipe and a scroll. They share the same input stream. A gesture recognizer needs to resolve that ambiguity before any animation starts, or you end up with cards dragging when the user meant to scroll the page behind them. The resolution usually comes from a threshold test on the dominant axis. Track the delta X and delta Y from the initial pointerdown position. If the horizontal component exceeds a set percentage of the vertical component — I use 55% as a rule of thumb — you commit to swipe mode and cancel any pending scroll. That number isn't sacred. It depends on your layout density and whether the swipe container shares screen real estate with scrollable content below it.
Once you've committed, you're tracking the card position in real time. Bind the card's transform directly to the pointer displacement with a dampening factor so it doesn't feel like it's moving faster than your finger. A multiplier somewhere between 0.8 and 1.0 usually feels right. Don't overthink it. Anything above 1.0 feels floaty and broken. Below 0.7 feels sluggish. The exact value shifts based on whether your card has momentum baked in or not. Then there's the release logic. This is where most implementations fail. When the user lifts their finger, you need to decide: does the card go back to center or does it fly off screen? The decision should be based on velocity, not position. I measured this empirically across dozens of devices. A card dragged to 60% of its width with a release velocity under 200 pixels per second snaps back. Above that threshold, it commits. Position-based thresholds are a trap because a user might drag halfway and stop out of caution, then expect it to fly off when they meant to hold. Velocity tells you intent better. Calculate velocity by sampling the delta positions over the last few frames before pointerup and dividing by the time elapsed. Smooth it with an exponential moving average so a single glitchy frame doesn't swing the decision. Then compare against your commitment threshold and animate accordingly.
The animation itself is simpler than most people make it. A cubic bezier ease-out with the card translating off-screen while rotating slightly and fading. Rotation around 8 to 12 degrees sells the effect without looking gimmicky. The timing should be around 300 to 400 milliseconds. Longer and it feels like the UI is hesitating. Shorter and the card pops off without satisfying follow-through. Haptic feedback matters more than you'd expect. A single sharp vibration at the moment of release — not during the drag — reinforces that the system registered the gesture. iOS handles this with UIImpactFeedbackGenerator. Android uses VibrationEffect. Fire it on release only, not on every move. Every move feels like the device is malfunctioning. I ran into a specific problem a while back building a swipe-based list for an inventory app. The cards were dense — thumbnails, labels, status badges — and users were accidentally triggering swipes while trying to tap individual items. The gesture recognizer was too aggressive because I'd set the dominant-axis threshold too low. Swipes fired during normal scrolling through the list. I ended up adding a dead zone check: if the pointer moved less than 8 pixels in the first 120 milliseconds after pointerdown, I treated it as a tap candidate and held off on swipe detection entirely. Only after that window closed did the swipe recognizer engage. That 120-millisecond grace period eliminated the accidental swipes without making intentional ones feel delayed. Most people never add that guard and just live with the frustration.
Get the Full Details

Accessibility is another area where swipe gets treated as an afterthought. Screen reader users can't interact with a swipe-only interface. You need keyboard equivalents — arrow keys for directional swipes, Enter to confirm. I've seen entire product launches ship with zero fallback. It's not just a nicety. It's a legal exposure in some jurisdictions, and it alienates a real segment of your user base that will simply stop using the app if they can't perform the core action. Performance is the quiet killer. Each swipe frame touches layout if you're not careful. Use transform and opacity only — those are GPU-composited and don't trigger reflow. If you animate left, top, width, or margin during a drag, you're forcing the browser or rendering engine to recalculate layout sixty times a second, and that's where the frame drops happen. I've profiled this on mid-range Android devices and watched framerates tank from 58 down to the 20s when non-composited properties entered the animation loop. The fix is almost always the same: keep the drag binding on transform translate only, and let the commit animation handle any additional property changes. There's also the matter of stack management. A single card swipe is straightforward. Ten cards stacked behind it requires maintaining state for each layer's visible offset and scale. The card at position N should appear smaller and slightly translated down to create the deck illusion. The math is simple — each layer gets a scale factor of 0.95 and a Y offset of 4 pixels multiplied by its depth — but get it wrong and the stack looks flat and lifeless or collapses into itself. I use a simple array where each entry tracks its index, current transform, and visibility state. When a card swipes away, the remaining cards shift down one index and animate to their new positions. The animation duration should be shorter for the shift — around 150 milliseconds — so it feels responsive rather than heavy.
Edge case worth mentioning: what happens when a user swipes rapidly in alternating directions? Some frameworks throttle the gesture recognizer and block the second swipe until the first animation completes. That feels terrible. The workaround is to let the second swipe interrupt the first animation mid-flight. Track the current animation state and allow a pointerdown event to cancel whatever's running. Then start the new swipe from the card's current position, not its reset position. This is harder to implement cleanly because you're fighting the animation library's assumptions, but it's the difference between a swipe that feels alive and one that feels stiff. Testing this across devices is non-negotiable. I've shipped swipes that worked fine on my test iPhone but felt like dragging through molasses on a Samsung Galaxy A-series. The touch sampling rates vary wildly — some phones report touch events at 60Hz, others at 120Hz or 240Hz. Your gesture recognizer needs to account for that variance. If you're locking your frame rate to the display refresh, you'll occasionally miss samples on lower-refresh hardware and the swipe will stutter. A fixed timestep interpolation approach for the pointer tracking smooths this out without adding much complexity. The bottom line is that the swipe looks simple because the good implementations hide all the work. The bad ones expose every flaw — late commits, jittery tracking, ambiguous scroll conflicts, missing accessibility, frame drops on older hardware. If you're building one, treat it as a real gesture recognition problem, not a UI decoration. The five components — recognition, tracking, release logic, animation, and haptics — each need their own attention and testing. Miss one and the whole thing feels wrong, even if four out of five are solid.
For a working reference, there's no single canonical implementation that covers all these cases out of the box. Most open-source libraries handle the basics and leave the edge cases to you. I usually start with the pointer events spec on MDN, build the recognizer from scratch for the specific constraints of my project, and only pull in a library when I'm reusing the same pattern across multiple products. The first swipe you build from scratch teaches you more than any library will. After that, the patterns repeat.
