Swipe Action Card
iOS-style swipe actions with progressive delete expansion and hold-to-delete interaction.
Overview
Swipe-to-reveal actions with iOS-style delete expansion and hold-to-delete interaction.
Learning
The main learning from this component is how to build a native-feeling swipe interaction using Motion.
While the final interaction looks simple, there are many small pieces working together. Here's what I learned while building it.
Drag constraints
Motion provides dragConstraints, but it only limits how far an element can be dragged. It doesn't decide what should happen when the user reaches a certain point.
For example, Motion doesn't provide built-in support for:
- Revealing action buttons after dragging a certain distance.
- Snapping to different positions based on drag progress.
- Expanding the delete button after a threshold.
- Triggering a delete action when the user holds the card.
- Showing or hiding UI based on drag distance.
All of these behaviors have to be implemented manually by reading the drag position and adding our own logic.
So, dragConstraints is not a complete swipe solution, it is more of a boundary for user input.
useMotionValue
Instead of storing the drag position in React state, I used a MotionValue.
This became the single source of truth for the entire interaction.
Everything responds to this one value:
- Card position
- Delete button width
- Button label opacity
- Edit button visibility
- Delete threshold
- Hold-to-delete logic
Because MotionValue updates outside React's render cycle, the interaction stays extremely smooth.
Function syntax of useTransform
Before this component, I mostly used the mapping syntax of useTransform.
This time I learned the callback version.
The callback syntax gives much more flexibility because we can write any JavaScript logic instead of simple linear interpolation.
This is useful whenever the animation has conditions, thresholds, or multiple stages.
useMotionValueEvent
Instead of storing every drag update inside React state, I listened directly to changes in the motion value.
This lets us react to drag progress without causing unnecessary React re-renders.
I used it to decide when the Edit button should appear and when it should disappear.
useSpring
The delete button width changes based on the drag distance.
Instead of using the transformed value directly, I wrapped it inside a spring.
This small change makes the expansion feel much more natural instead of mechanically following the cursor.
Building multi-stage interactions
The most interesting part of this component wasn't the animation itself. It was coordinating multiple interaction states.
As the user drags:
- The card moves.
- The Edit button fades away.
- The Delete button starts expanding.
- The label fades in.
- Holding beyond a threshold triggers the delete animation.
All of these are driven from a single drag gesture, which makes the interaction feel cohesive rather than a collection of separate animations.
Just a heads up
These components are part of my learning journey. I'm building them to practice motion, interactions, and UI engineering, so think of them as experiments rather than production-ready components.
Most of the ideas are inspired by incredible work from designers and developers across the internet. I'm not the original creator of those concepts, and I've added proper credit and links to the original sources wherever I could.