card-motion
v0.2.0
Published
Juicy playing-card animations for React — shuffle, deal, play, 3D tilt and a WebGL background. Powered by GSAP.
Maintainers
Readme
🃏 card-motion
Juicy playing-card animations for React.
Shuffle, deal, select and play with GSAP timelines — a ready-made card table, plus the headless engine to build your own game.
Features
<CardTable>— a full deck with shuffle, deal, select, play and reset, wired up.- Headless engines —
useCardTable(deck/hand/table) anduseCardPiles(any piles: solitaire, discards, foundations) own the state and the GSAP timelines; you render the cards. - Drag & drop — primitives for board games: the library owns the pointer mechanics and snap-back, you own the rules.
- Composable layouts — swap in
fan/row/stack, or write your own. - Accessible — keyboard-operable, ARIA roles, a live region, and
prefers-reduced-motionsupport. - TypeScript-first, tree-shakeable ESM + CJS, runs in Next.js (App Router) as a client component.
Install
pnpm add card-motion gsapreact and gsap are peer dependencies. Import the stylesheet once, in your app entry:
import 'card-motion/styles.css';Quick start
import { CardTable } from 'card-motion';
import 'card-motion/styles.css';
export default function App() {
return (
<div style={{ position: 'relative', width: '100vw', height: '100vh' }}>
<CardTable handSize={8} />
</div>
);
}<CardTable> fills its positioned parent, so give it a sized, position: relative container.
| Prop | Default | Description |
| --- | --- | --- |
| handSize | 8 | Cards dealt into the hand. |
| cardWidth | 96 | Card width in px (auto-shrinks on narrow screens). |
| controls | true | Show the built-in control bar. |
| selectable | true | Click a hand card to select / deselect it. |
| deck | full 52 | Custom deck (CardData[]). |
Drive it imperatively with a ref (CardTableHandle): shuffle, deal, play, playSelected, clearTable, reset, toggleCard.
Headless
Render your own cards and controls; the hook owns the state, the timelines and selection.
import { useCardTable, Card } from 'card-motion';
function Table() {
const { cards, stageRef, registerCard, deal, playSelected, toggleCard, selected, counts } =
useCardTable({ handSize: 7 });
return (
<div className="cm-stage" ref={stageRef}>
{cards.map((c) => (
<Card
key={c.id}
ref={(node) => registerCard(c.id, node)}
rank={c.rank}
suit={c.suit}
selected={selected.has(c.id)}
onClick={() => toggleCard(c.id)}
style={{ position: 'absolute', top: 0, left: 0 }}
/>
))}
</div>
);
}For anything beyond deck/hand/table, useCardPiles manages arbitrary piles with the same
model. You declare each pile (its anchor and layout) and move cards with move / draw /
gather / shuffle. Cards can carry any payload with a numeric id — the engine only reads id.
const { piles, move, draw, gather } = useCardPiles({
piles: {
deck: { anchor: (s) => ({ x: s.width / 2, y: 80 }), layout: stackLayout },
hand: { anchor: (s) => ({ x: s.width / 2, y: s.height - 120 }), layout: fanLayout },
},
});Building blocks
- Drag & drop —
<DragDropProvider onDrop>+<DropZone id accepts>+<DraggableCard id zone>to build solitaire and friends.onDropfires on a valid drop; returnfalseto reject (the card springs back). useCardDrag— pointer dragging for engine-positioned cards. You give it aresolveDrop(id, point, stage)that picks a target (or rejects) and anonDropthat applies it; taps and drags are told apart, so click-to-select keeps working.useCardInspect+<CardInspectLayer>— tap / hold / hover (or callopen()from any event) to magnify a card. The hook handles the gesture and is movement-aware, so it yields to a drag;<CardInspectLayer>animates a FLIP magnify out of the source card over a dim backdrop. You render the enlarged content, the library owns the motion.fan/row/stack— layout factories, e.g.fan({ spread: 0.6, maxSpacing: 98 }). Zero-config instances (fanLayout,rowLayout,stackLayout) are exported too.<Card>— the card visual: corner indices, big pip, an optional holographicfoil, and a pointer-driven 3Dtilt.<BackgroundShader>— an optional fullscreen WebGL swirl.<BackgroundShader colors={{ deep, warm, cool }} speed={1.2} />.<DeckReveal>— a modal that spreads a pile so the player can browse and pick a card.- Timings — every engine takes an optional
motionconfig (moveDuration,dealEase,selectLift, …); omit it and you get the built-in choreography. - Utilities —
buildDeck(),shuffleInPlace(),cardLabel(rank, suit),SUITS,RANKS.
Accessibility
<CardTable> is keyboard- and screen-reader-friendly out of the box: arrow-key roving focus
across the hand, Enter / Space to select, ARIA roles and names, a polite live region for
counts, and a visible focus ring. When the user prefers reduced motion, animations snap to their
end state instead of playing.
Requirements
- React 18 or 19
- GSAP 3.12+ (peer dependency)
- A browser with WebGL for
<BackgroundShader>(it degrades gracefully otherwise)
