Draggable Card Stack
A stack whose top card can be thrown away — or advanced with a button.
- stacks
- drag
- swipe
- deck
- review
Drag me sideways
Or use the buttons — they do the same thing.
Spring back
A hesitant drag returns the card to the deck.
Looping
Dismissed cards go to the back by default.
Sample deck
Usage
import { DraggableCardStack } from "@pinky-ui/layouts";
<DraggableCardStack threshold={110} loop>
{cards.map((card) => (
<JellyCard key={card.id}>{card.body}</JellyCard>
))}
</DraggableCardStack>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| threshold | number | 110 | Drag distance in px past which the card is dismissed. |
| rotation | number | 12 | Maximum rotation while dragging. |
| loop | boolean | true | Send dismissed cards to the back instead of removing them. |
| onDismiss | (index: number) => void | — | Called with the dismissed card's index. |
| controls | boolean | true | Renders previous/next buttons. |
Accessibility
- Drag is never the only way through: the previous/next buttons do everything the gesture does.
- Dismissals are announced through a polite live region.
- Cards keep their own semantics and focus order.
Performance
- Only three cards are rendered at a time regardless of deck size.
- Drag is handled by transform motion values — no React state per frame.
Reduced motion
With prefers-reduced-motion: reduce, dragging is disabled entirely and the buttons advance the deck with no animation.
When to use
- Reviewing items one at a time
- Onboarding sequences and tips
- Playful browsing of a small set
When not to use
- Content users need to compare or return to
- Anything important enough that dismissing it by accident would matter
- Long lists — dragging through fifty cards is a chore
Skill
Purpose
A deck whose top card can be thrown away, for reviewing things one at a time.
Good for
- reviewing items in sequence
- onboarding sequences and tips
- playful browsing of a small set
Avoid for
- content users need to compare or come back to
- anything important enough that an accidental dismissal would matter
- long lists — dragging through fifty cards is a chore, not a feature
How many items
Three to twenty.
Mobile
Where it works best; the gesture is native to touch. Keep the buttons visible anyway — they are not a fallback, they are the primary interface for everyone who does not drag.
Motion intensity
threshold={110} and rotation={12} are tuned so a decisive flick dismisses and a hesitant one springs back. Lowering the threshold much below 80 makes accidental dismissals common.
Accessibility
Drag is never the only way through. The previous/next buttons do everything the gesture does, dismissals are announced through a polite live region, and cards keep their own focus order. If you set controls={false}, you must supply your own equivalent controls — a drag-only component is one some people simply cannot use.
Performance
Three cards render at a time regardless of deck size. Dragging runs on transform motion values with no React state per frame.
Composes with
Any card. Jelly Card is fine here because only one card is interactive at a time — this is one of the few places a heavier surface is affordable.