Skip to content
Pinky UI

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

usage
import { DraggableCardStack } from "@pinky-ui/layouts";

<DraggableCardStack threshold={110} loop>
  {cards.map((card) => (
    <JellyCard key={card.id}>{card.body}</JellyCard>
  ))}
</DraggableCardStack>

Props

PropTypeDefaultDescription
thresholdnumber110Drag distance in px past which the card is dismissed.
rotationnumber12Maximum rotation while dragging.
loopbooleantrueSend dismissed cards to the back instead of removing them.
onDismiss(index: number) => voidCalled with the dismissed card's index.
controlsbooleantrueRenders 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.

Open this skill on its own page