Card deck Pro
A pile of cards to throw. Pick up the top one and it turns about where you're holding it, like a real card: hold it high and it leans into the drag, low and it leans back. Let go gently and it springs back onto the pile. Throw it and it flies off at the speed you gave it, while the next one rises.
Installation
npx @neelshha/ui@latest login
npx @neelshha/ui@latest add card-deckUsage
import { CardDeck } from "@/components/ui/card-deck";
<CardDeck
aria-label="Postcards"
onSwipe={(index, direction) => sort(index, direction)}
>
<Postcard city="Lisbon" />
<Postcard city="Tokyo" />
<Postcard city="Reykjavík" />
</CardDeck>Dragging a card nearly half its width throws it, and so does a quick flick of any length. A card dragged out and pulled back stays. Only the top card takes the pointer; the ones under it wait, fanned a little, and loosen when you reach for the pile.
Buttons
The ref's swipe and undo work the deck from buttons of your own. Undo brings a card back from the side it went.
const deck = useRef<CardDeckHandle>(null);
<CardDeck ref={deck} loop aria-label="Postcards">…</CardDeck>
<button onClick={() => deck.current?.swipe("left")}>Pass</button>
<button onClick={() => deck.current?.undo()}>Undo</button>
<button onClick={() => deck.current?.swipe("right")}>Keep</button>Running out
Without loop the deck runs out and shows empty. onSwipe and onUndo keep the tally right, undo included.
<CardDeck
aria-label="Cities to visit"
onSwipe={(index, direction) => tally(index, direction, 1)}
onUndo={(index, direction) => tally(index, direction, -1)}
empty={<Summary />}
>
{cities.map((city) => <Postcard key={city.name} city={city} />)}
</CardDeck>Keyboard
The deck is one tab stop. The Left and Right arrows throw the top card that way, and Backspace brings the last one back. Only the top card can be reached; the rest are inert. Screen readers hear which card is on top, like "Card 2 of 5", so give the deck a name with aria-label.
Source
API
Other props go to the outer div. Cards take their size from --ns-deck-width and --ns-deck-ratio. With reduced motion a thrown card goes at once, and nothing springs.
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | The cards, top first. | |
onSwipe | (index: number, direction: "left" | "right") => void | Runs as a card is thrown, with its place among the children and the way it went. | |
onUndo | (index: number, direction: "left" | "right") => void | Runs as undo brings a card back, with the way it had gone. | |
loop | boolean | false | Thrown cards tuck back under the pile instead of leaving it. |
empty | ReactNode | Shown once every card is gone. | |
depth | number | 3 | How many cards show under the top one. |
ref | Ref<{ swipe(direction), undo() }> | For buttons of your own. |