Card stack

Review a deck one card at a time, with a throw and an undo.

pnpm dlx shadcn@latest add @uiarc/card-stack
Live · keyboard ready

Spring offsite

0 shortlisted · 6 to review

LIS14–17 Oct

Lisbon

Portugal

Tiled hills, long lunches, and a riverside workshop space for 18.

  • 3h 35m direct from Berlin
  • 22° and mostly sunny
  • From €780 per person

Drag the top card, or use the left and right arrow keys to skip or shortlist it. Backspace undoes the last decision.

  • One-by-one triage such as shortlisting, review queues, or onboarding picks.
  • Decks where swiping left or right should be undoable.
  • Use a list or sortable-data-table when people compare items side by side.
  • Use carousel to browse items without deciding.
  • Use swipe-actions for quick actions on rows in a list.

Installation

Add Card stack with the shadcn CLI, or copy the source by hand.

pnpm dlx shadcn@latest add @uiarc/card-stack

Adds the component and its local dependencies, and installs motion, lucide-react. First time? Add the @uiarc registry to components.json, or use the full URL:

example.tsx
import { CardStack } from "@/components/arc/card-stack/card-stack"; const cities = [{ id: "lis", name: "Lisbon" }, { id: "osl", name: "Oslo" }, { id: "kyo", name: "Kyoto" }]; export function Shortlist() {  return (    <CardStack      items={cities}      getKey={city => city.id}      getLabel={city => city.name}      renderCard={city => <h3>{city.name}</h3>}      labels={{ left: "Skip", right: "Shortlist" }}      onDecide={(city, decision) => save(city.id, decision)}    />  );}

CardStack

A deck reviewed one card at a time by swiping left or right, with buttons, undo, and reset.

PropTypeDefaultDescription
itemsRequiredT[]–Cards in order.
getKeyRequired(item: T) => string–Stable key per item.
getLabelRequired(item: T) => string–Short name for labels and announcements, such as "Lisbon".
renderCardRequired(item: T) => ReactNode–Card content.
onDecide(item: T, decision: "left" | "right") => void–Called when a card is swiped or decided by button or key.
onUndo(item: T, decision: "left" | "right") => void–Called when the last decision is undone.
onReset() => void–Called when all cards come back.
labels{ left: string; right: string }{ left: "Pass", right: "Keep" }Action names on the buttons and drag stamps.
outcomes{ left: string; right: string }–Past tense of each action for announcements, such as "skipped".
labelstring"Cards"Accessible name for the stack.
renderEmpty(reset: () => void) => ReactNode–Shown once every card is decided. Defaults to a Start over button.
classNamestring–Class for the root.
ArrowLeftorArrowRight
Decides the top card left or right when the stack is focused.
BackspaceorCmd+ZorCtrl+Z
Undoes the last decision.
  • The stage is a focusable role="group" with aria-roledescription="card stack" and a hidden hint describing drag and keys.
  • Each card is labelled like "Lisbon, 1 of 3".
  • Decisions, undo, and reset are announced in a polite status region.
  • Buttons mirror every gesture, so swiping is never required.
  • The top card follows the drag with tilt and stamps; a flick or far drag throws it off with its velocity.
  • Cards beneath rise into place, and undo flies a card back from where it left.
  • Reduced motion fades cards out and in at home without travel.
  • The stage is min(100%, 20rem) wide; below 26rem the cards get shorter, and below 22.5rem Undo becomes icon-only.
  • Cards use touch-action pan-y, so vertical swipes still scroll the page.
  • Only the top four cards render in the stage, however long the deck.
  • Drag and throw run on motion values without per-frame React updates.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Use for one-by-one triage: shortlisting, review queues, onboarding picks. Use a list or sortable-data-table when people compare items side by side.
  • Persist decisions from onDecide and reverse them in onUndo.

The full library index for assistants is at /llms.txt.