npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-ttrpg-cards

v0.3.1

Published

Plug-and-play React component for dealing 3D playing cards with physics

Downloads

667

Readme

🃏 react-ttrpg-cards

Plug-and-play React component for dealing 3D playing cards with physics.

Drop a full-deck card dealing experience into any React app. Cards spawn off-screen and fly onto a target element with realistic Rapier physics — complete with procedural textures, five built-in themes, multiple deal patterns, and synthesised sound effects. Zero image assets required.

✨ Features

  • Physics-driven dealing — Cards launch with impulse forces, tumble through the air, and settle naturally using Rapier rigid-body physics.
  • 5 deal patternsfan, stack, hand, row, and reveal — each with staggered timing and organic randomisation.
  • 5 built-in themesclassic, dark, ornate, minimalist, and royal with full PBR material support.
  • Procedural card textures — Standard pips, ranks, and card backs are canvas-generated at runtime. No sprite sheets needed.
  • Flexible texture modesprocedural, procedural-bg, svg, and svg-bg for mixing generated graphics with custom assets.
  • Synthesised sound — Web Audio card snap, slide, flip, and shuffle sounds. No audio files to bundle.
  • Flip & clear — Flip dealt cards face-up ↔ face-down, then fade-clear the table.
  • Target-aware — Cards land relative to any DOM element's bounding rect, with live resize tracking.
  • Accessible — ARIA live regions announce deals; respects prefers-reduced-motion.
  • TypeScript-first — Full type exports with discriminated unions for card identity.
  • React 18 + 19 — Works with both major React versions.
  • "use client" — SSR/RSC safe out of the box.

📦 Installation

npm install react-ttrpg-cards

Peer dependencies

These must be installed in your project:

npm install react react-dom three @react-three/fiber @react-three/rapier

| Package | Version | |----------------------|---------------| | react | ^18 \|\| ^19 | | react-dom | ^18 \|\| ^19 | | three | ≥ 0.171.0 | | @react-three/fiber | ≥ 9.0.0 | | @react-three/rapier| ≥ 1.5.0 |

🚀 Quick Start

import { useRef } from 'react';
import { useCardDeal } from 'react-ttrpg-cards';
import type { Card } from 'react-ttrpg-cards';

function App() {
  const tableRef = useRef<HTMLDivElement>(null);

  const { deal, isDealing, result, CardOverlayPortal } = useCardDeal({
    targetRef: tableRef,
  });

  const cards: Card[] = [
    { suit: 'spades', rank: 'A' },
    { suit: 'hearts', rank: 'K' },
    { suit: 'diamonds', rank: 'Q' },
  ];

  return (
    <>
      <div ref={tableRef} style={{ width: 600, height: 400 }}>
        Game Table
      </div>
      <button onClick={() => deal(cards, { pattern: 'fan' })} disabled={isDealing}>
        Deal
      </button>
      {CardOverlayPortal}
    </>
  );
}

Important: {CardOverlayPortal} must be rendered somewhere in your component tree. It creates a fixed full-screen canvas overlay that the 3D cards render into.

📖 API Reference

useCardDeal(options): UseCardDealReturn

The primary hook for dealing cards.

Options

interface UseCardDealOptions {
  /** Ref to the HTML element cards will land on (required) */
  targetRef: React.RefObject<HTMLElement>;
  /** Theme configuration */
  config?: CardThemeConfig;
  /** Texture rendering mode */
  textureConfig?: TextureConfig;
  /** Camera angle offset */
  cameraAngle?: CameraAngle;
  /** Enable sound — `true` for defaults, or pass a config object */
  sound?: boolean | CardSoundConfig;
  /** Hard timeout (ms) before forcing result. Default: 6000 */
  timeout?: number;
  /** CSS z-index for the overlay canvas. Default: 9999 */
  zIndex?: number;
  /** Callback when all cards have settled */
  onDealComplete?: (result: DealResult) => void;
}

Return value

interface UseCardDealReturn {
  /** Deal a set of cards */
  deal: (cards: Card[], opts?: DealOptions) => void;
  /** Flip specific cards by index (toggles face-up ↔ face-down) */
  flip: (indices: number[]) => void;
  /** Fade out and remove all cards */
  clear: () => void;
  /** True while cards are animating / settling */
  isDealing: boolean;
  /** Latest deal result (persists until clear() is called) */
  result: DealResult | null;
  /** Render this in your component tree to display the 3D overlay */
  CardOverlayPortal: React.ReactNode;
}

Deal options

interface DealOptions {
  /** Layout pattern. Default: 'fan' */
  pattern?: 'fan' | 'stack' | 'hand' | 'row' | 'reveal';
  /** Deal face-up or face-down. Default: true */
  faceUp?: boolean;
}

<CardOverlay /> (low-level)

The underlying component if you need full control. Most users should prefer useCardDeal.

<CardOverlay
  cards={cards}
  pattern="fan"
  faceUp={true}
  targetRef={tableRef}
  onDealComplete={(result) => console.log(result)}
  config={{ theme: 'dark' }}
  sound={{ volume: 0.5 }}
/>

🃏 Card Types

Cards use a discriminated union:

// Standard playing card
interface StandardCard {
  suit: 'hearts' | 'diamonds' | 'clubs' | 'spades';
  rank: 'A' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | 'J' | 'Q' | 'K';
}

// Joker
interface JokerCard {
  type: 'joker';
  variant?: 'red' | 'black';
}

type Card = StandardCard | JokerCard;

Type guards are exported for convenience:

import { isStandardCard, isJokerCard } from 'react-ttrpg-cards';

if (isStandardCard(card)) {
  console.log(card.suit, card.rank);
}

🎨 Themes

Five built-in themes control card face colours, pip colours, edge colour, card back pattern, and PBR material properties:

| Theme | Card Face | Back Pattern | Character | |--------------|-----------|-------------|-----------------------| | classic | White | Crosshatch | Traditional blue & gold | | dark | Dark navy | Diamond | Sleek dark mode | | ornate | Warm cream| Ornate | Vintage, gilded edges | | minimalist | Pure white| Solid | Clean, modern | | royal | Off-white | Royal | Regal blue & gold |

const { deal, CardOverlayPortal } = useCardDeal({
  targetRef: tableRef,
  config: {
    theme: 'dark',
    // Override individual colours:
    redPipColor: '#ff6b6b',
    edgeColor: '#333',
    // Custom card back image (overrides theme pattern):
    backImage: '/my-card-back.png',
    // Corner radius as fraction of half-height:
    cornerRadius: 0.08,
  },
});

🖼️ Texture Modes

Control how card face textures are generated:

| Mode | Description | |-----------------|--------------------------------------------------| | procedural | Pure canvas-generated pips & ranks (default, zero assets) | | procedural-bg | Procedural overlay on a JPG/PNG background image | | svg | User-supplied SVG foreground replaces procedural pips | | svg-bg | SVG foreground composited over a background image |

const { deal, CardOverlayPortal } = useCardDeal({
  targetRef: tableRef,
  textureConfig: {
    mode: 'procedural-bg',
    backgroundImage: '/card-texture.jpg',
  },
});

🎯 Deal Patterns

| Pattern | Description | |----------|--------------------------------------------------------| | fan | Cards arc in a semicircular spread | | stack | Cards dealt to a slightly offset pile | | hand | Curved held-hand formation with slight overlap | | row | Evenly spaced horizontal line | | reveal | Single dramatic face-up reveal (or small set) |

deal(cards, { pattern: 'reveal', faceUp: true });

🔊 Sound

All sounds are procedurally synthesised using the Web Audio API — no audio files needed.

| Sound | Trigger | |----------|-------------------------------| | shuffle | Automatic at deal start | | deal | Each card hits the surface | | slide | Card slides along the table | | flip | Card flips face-up / face-down|

// Enable with defaults
const hook = useCardDeal({ targetRef, sound: true });

// Custom volume (0–1)
const hook = useCardDeal({ targetRef, sound: { volume: 0.5 } });

// Disable (default)
const hook = useCardDeal({ targetRef, sound: false });

📸 Camera

Adjust the camera viewing angle with small offsets:

const hook = useCardDeal({
  targetRef,
  cameraAngle: {
    x: 2,   // horizontal offset (left/right tilt)
    z: 3,   // depth offset (forward/backward tilt)
  },
});

♻️ Flip & Clear

const { deal, flip, clear, result } = useCardDeal({ targetRef });

// Deal face-down
deal(cards, { faceUp: false });

// Later — flip the first and third cards face-up
flip([0, 2]);

// Clear the table with a fade-out
clear();

🏗️ Architecture

src/
├── index.ts                   # Public API exports
├── types.ts                   # All TypeScript types + type guards
├── use-card-deal.tsx          # Primary hook
├── components/
│   ├── card-overlay.tsx       # Full-screen Canvas + orthographic camera
│   ├── physics-scene.tsx      # Rapier Physics world + card spawning
│   └── card-body.tsx          # Individual card rigid body + materials
├── animation/
│   ├── deal-patterns.ts       # Spawn config generators for each pattern
│   └── flip-animation.ts      # Card flip animation logic
├── geometry/
│   └── card-geometry.ts       # Rounded-rect card mesh with UV mapping
├── textures/
│   ├── texture-generator.ts   # Procedural card face renderer
│   └── back-texture.ts        # Card back pattern renderer
├── themes/
│   ├── theme-definitions.ts   # Built-in theme colour palettes
│   └── apply-theme.ts         # Theme merging + override logic
└── sound/
    └── card-sound.ts          # Web Audio procedural sound engine

🛠️ Development

# Install dependencies
npm install

# Build the library (uses bunchee)
npm run build

# Watch mode
npm run dev

# Run tests
npm test

# Type-check
npm run typecheck

Running the demo

cd demo
npm install
npm run dev

The demo app provides a full interactive playground with theme switching, deal pattern selection, target zone presets, card picking, flip, and clear controls.

📋 Requirements

  • Node.js ≥ 20
  • React 18 or 19
  • A browser with WebGL support

📄 License

MIT © strangeworlder