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

deckfx

v1.0.1

Published

A high-performance, interaction-rich React library for building card and board games

Downloads

274

Readme

DeckFX Demos

npm version license Node.js

Interactive demo showcase for the DeckFX library

🎮 Live Demo📦 npm Package💻 Examples

This repository contains interactive demonstrations of the DeckFX library - a high-performance, interaction-rich React library for building card and board games.

DeckFX provides production-ready components for 3D card rendering, layout management, and dice rolling. Built with React, Three.js, and Framer Motion for smooth animations and interactions.

Note: This repository uses the published deckfx npm package. For the library source code, see the npm package.

Features

  • 🎴 3D Card Rendering - Realistic card physics with tilt, glare, and flip animations
  • 🎯 Layout Engine - Multiple layout modes (fan, grid, stack, cascade, messy)
  • 🎲 3D Dice System - Support for D4, D6, D8, D10, D12, and D20 with physics
  • 🖱️ Drag & Drop - Built-in drag interactions with snap-to-origin
  • Async Validation - Server-side validation for all card interactions (drag, drop, reorder, draw)
  • 🎨 Customizable - Full control over card appearance, animations, and interactions
  • 📱 TypeScript - Fully typed with comprehensive type definitions
  • Performance - Optimized for smooth 60fps animations

Installation

npm install deckfx framer-motion three @react-three/fiber @react-three/drei lucide-react

Quick Start

import { Card, CardZone, DiceRoller } from 'deckfx';

function App() {
  return (
    <CardZone layout="fan">
      <Card faceUp={true} width={120} height={170}>
        <div>Ace of Spades</div>
      </Card>
    </CardZone>
  );
}

Examples

Check out the live demo to see DeckFX in action:

  • TCG Battle - Trading card game battlefield with drag & drop
  • Hand Physics - Interactive card hand with hover effects
  • Inventory Grid - Grid-based card inventory system
  • Solitaire - Classic solitaire game implementation
  • RPG Adventure - Role-playing game card system
  • War - Card game with deck management
  • Blackjack - Casino card game
  • Dice Roller - 3D dice with physics simulation

Core Components

Card

The fundamental building block. Handles 3D tilt, glare effects, flipping animations, and drag interactions.

<Card 
  faceUp={true}
  imageUrl="/card-art.jpg"
  width={200}
  height={300}
  showGlare={true}
  maxTilt={15}
  drag
  dragSnapToOrigin
  onClick={() => console.log('clicked')}
/>

Props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | faceUp | boolean | true | Whether the card face is visible | | width | number | 200 | Card width in pixels | | height | number | 300 | Card height in pixels | | imageUrl | string | - | Background image for the card | | showGlare | boolean | true | Enable holographic glare effect | | maxTilt | number | 15 | Maximum 3D tilt angle in degrees | | rotation | number | 0 | Z-axis rotation in degrees | | drag | boolean | false | Enable drag interactions | | onValidateDragStart | (cardId: string, cardData?: any) => Promise<ValidationResult> | - | Async validator for drag start | | onValidateDragEnd | (cardId: string, fromZone: string, toZone: string, cardData?: any) => Promise<ValidationResult> | - | Async validator for drag end | | onValidationError | (error: string) => void | - | Callback for validation errors | | cardId | string | - | Card identifier for validation | | cardData | any | - | Additional data to pass to validators | | onFlip | (newFaceUp: boolean) => void | - | Callback when flip animation starts | | onFlipComplete | (faceUp: boolean) => void | - | Callback when flip animation completes | | flipDuration | number | - | Flip animation duration in ms | | flipDirection | 'horizontal' \| 'vertical' | 'horizontal' | Direction of flip animation | | selectable | boolean | false | Whether card can be selected | | selected | boolean | false | Controlled selected state | | onSelect | (cardId: string, event?: MouseEvent) => void | - | Callback when card is selected | | counters | Counter[] | - | Array of counters to display on card | | renderCounter | (counter: Counter) => ReactNode | - | Custom counter renderer | | stackId | string | - | ID of the stack this card belongs to | | stackOrder | number | - | Order within stack (0 = bottom) | | isStackTop | boolean | - | Whether card is on top of stack |

CardZone

A layout engine that automatically positions child cards.

<CardZone 
  layout="fan" 
  fanAngle={15} 
  fanArcHeight={20}
>
  {cards.map(card => <Card key={card.id} {...card} />)}
</CardZone>

Layouts:

  • stack - Cards piled on top (deck)
  • grid - Standard grid (inventory)
  • linear-horizontal - Row of cards
  • linear-vertical - Column of cards
  • messy - Random rotation (discard pile)
  • fan - Arc layout (player hand)
  • cascade-vertical - Solitaire-style cascade
  • cascade-horizontal - Horizontal cascade

Capacity Props: | Prop | Type | Default | Description | |------|------|---------|-------------| | maxCards | number | - | Maximum number of cards allowed | | currentCount | number | - | Manual count override (auto-calculated if not provided) | | showCapacity | boolean | false | Show capacity badge | | capacityWarningThreshold | number | 0.8 | Threshold (0-1) for warning color | | onCapacityReached | () => void | - | Callback when capacity is reached | | dropZoneValid | boolean | - | External control for drop zone validity | | onDragOver | (isOver: boolean) => void | - | Callback when dragging over zone |

DiceRoller

A 3D dice engine supporting D4, D6, D8, D10, D12, and D20.

import { DiceRoller, DieState, DieConfig } from 'deckfx';

const [dice, setDice] = useState<DieState[]>([
  { id: 1, type: 'd20', value: 1, target: 20, isRolling: false }
]);

const config: DieConfig = {
  color: '#ffffff',
  duration: 2000,
  size: 1,
  spinForce: 5
};

<DiceRoller 
  dice={dice}
  config={config}
  zoom={15}
  onRollComplete={(id) => {
    setDice(prev => prev.map(d => 
      d.id === id ? { ...d, isRolling: false } : d
    ));
  }}
/>

DeckSearchModal

A pre-built modal for searching through card collections.

<DeckSearchModal
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  title="Search Deck"
  cards={deckCards}
  searchProperty="title"
  onCardSelect={(card) => handleSelect(card)}
  renderCard={(card) => <Card {...card} />}
/>

Deck

A visual representation of a card deck with draw functionality and validation support.

<Deck 
  count={52}
  onDraw={() => {
    // Handle card draw
  }}
  onValidateDraw={async () => {
    // Validate if draw is allowed
    return { valid: true };
  }}
  onValidationError={(error) => {
    // Handle validation errors
    console.error(error);
  }}
/>

Props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | count | number | - | Number of cards in the deck | | onDraw | () => void | - | Callback when deck is clicked | | thickness | number | 1 | Visual thickness per card | | onValidateDraw | () => Promise<ValidationResult> | - | Async validator for draw operation | | onValidationError | (error: string) => void | - | Callback for validation errors |

Hand

A specialized component for player hands with hover effects, reordering, and validation.

<Hand 
  spacing={-40} 
  arcHeight={30} 
  maxFanAngle={15}
  sortable={true}
  onReorder={(fromIndex, toIndex) => {
    // Handle reorder
  }}
  onValidateReorder={async (cardId, fromIndex, toIndex) => {
    // Validate if reorder is allowed
    return { valid: true };
  }}
  onValidationError={(error) => {
    // Handle validation errors
    console.error(error);
  }}
>
  {handCards.map(card => <Card key={card.id} {...card} />)}
</Hand>

Props:

| Prop | Type | Default | Description | |------|------|---------|-------------| | spacing | number | -50 | Card spacing (negative for overlap) | | arcHeight | number | 30 | Height of the arc | | maxFanAngle | number | 15 | Maximum rotation angle | | hoverScale | number | 1.2 | Scale on hover | | hoverYOffset | number | -100 | Vertical offset on hover | | straightenOnHover | boolean | true | Straighten card on hover | | sortable | boolean | false | Enable card reordering | | onReorder | (fromIndex: number, toIndex: number) => void | - | Callback when cards are reordered | | onValidateReorder | (cardId: string, fromIndex: number, toIndex: number) => Promise<ValidationResult> | - | Async validator for reorder operation | | onValidationError | (error: string) => void | - | Callback for validation errors |

TargetingArrow

An SVG arrow for targeting interactions (like Hearthstone).

<TargetingArrow 
  start={{ x: 100, y: 100 }}
  end={{ x: 300, y: 200 }}
  color="#ff0000"
/>

Card Flip API

Programmatic control over card flip animations.

import { Card, CardRef } from 'deckfx';

const cardRef = useRef<CardRef>(null);

<Card
  ref={cardRef}
  faceUp={isFaceUp}
  onFlip={(newFaceUp) => console.log('Flipping to', newFaceUp)}
  onFlipComplete={(faceUp) => console.log('Flip complete')}
  flipDuration={500}
  flipDirection="horizontal"
/>

// Programmatic flip
cardRef.current?.flipTo(true);

Card Selection

Context-based selection management with multi-select support.

import { SelectionProvider, useCardSelection } from 'deckfx';

<SelectionProvider
  selectionMode="multi"
  onSelectionChange={(selectedIds) => console.log(selectedIds)}
>
  <Card cardId="card-1" selectable>Card 1</Card>
  <Card cardId="card-2" selectable>Card 2</Card>
</SelectionProvider>

// Hook usage
const { selectedIds, selectCard, clearSelection } = useCardSelection();

Card Counters

Display counters and tokens on cards.

<Card
  counters={[
    { id: 'damage', type: 'damage', value: 3, color: 'red', position: 'top-right' },
    { id: 'mana', type: 'mana', value: 2, color: 'blue', position: 'top-left' }
  ]}
/>

Zone Capacity

Add capacity limits and visual feedback to zones.

<CardZone
  zoneId="hand"
  maxCards={7}
  showCapacity={true}
  capacityWarningThreshold={0.8}
  onCapacityReached={() => console.log('Zone full!')}
>
  {cards.map(card => <Card key={card.id} {...card} />)}
</CardZone>

Card Stacking

Group cards into stacks with drag and peek functionality.

import { CardStack } from 'deckfx';

<CardStack stackId="stack-1" peekOffset={20}>
  <Card cardId="card-1" stackId="stack-1" stackOrder={0} isStackTop />
  <Card cardId="card-2" stackId="stack-1" stackOrder={1} />
  <Card cardId="card-3" stackId="stack-1" stackOrder={2} />
</CardStack>

Validation

DeckFX provides async validation for all card interactions, allowing you to validate actions against server-side rules, game state, or API endpoints.

Validation Context (Recommended)

For applications with multiple components, use the ValidationProvider to centralize validation configuration:

import { ValidationProvider, Card, CardZone } from 'deckfx';

function GameBoard() {
  return (
    <ValidationProvider
      onValidateDragEnd={async (cardId, fromZone, toZone) => {
        // Centralized validation logic
        const response = await fetch('/api/validate-move', {
          method: 'POST',
          body: JSON.stringify({ cardId, fromZone, toZone })
        });
        const data = await response.json();
        return { valid: data.allowed, error: data.error };
      }}
      onValidationError={(error) => {
        showToast(error);
      }}
    >
      <CardZone zoneId="hand" layout="fan">
        <Card cardId="card-1" drag>Card 1</Card>
      </CardZone>
      <CardZone zoneId="board" layout="grid">
        {/* Drop zone */}
      </CardZone>
    </ValidationProvider>
  );
}

Prop-Based Validation (Legacy)

You can also pass validators directly to components:

import { Card, ValidationError, useValidationError } from 'deckfx';

function GameBoard() {
  const { error, showError, ErrorComponent } = useValidationError();
  
  return (
    <>
      <Card
        drag
        cardId={card.id}
        onValidateDragEnd={async (cardId, fromZone, toZone) => {
          // Server-side validation
          const response = await fetch('/api/validate-move', {
            method: 'POST',
            body: JSON.stringify({ cardId, fromZone, toZone })
          });
          const data = await response.json();
          return { valid: data.allowed, error: data.error };
        }}
        onValidationError={showError}
      />
      {ErrorComponent}
    </>
  );
}

Zone Identification

Use the zoneId prop on CardZone to identify zones for drag and drop validation:

<CardZone zoneId="hand" layout="fan">
  <Card drag cardId="card-1">Card 1</Card>
</CardZone>

<CardZone zoneId="board" layout="grid">
  {/* Drop target */}
</CardZone>

The zoneId is automatically set as a data-zone-id attribute, which is used by the drag validation system to identify source and target zones.

Validation Types

  • Drag Start - Validate before a card can be picked up
  • Drag End - Validate before a card can be dropped (snaps back on failure)
  • Reorder - Validate before cards can be reordered in a hand
  • Draw - Validate before a card can be drawn from a deck

Validation Result

All validators return a Promise<ValidationResult>:

type ValidationResult = {
  valid: boolean;
  error?: string;
};

For detailed validation documentation, see the full documentation.

Peer Dependencies

DeckFX requires the following peer dependencies:

{
  "react": "^18.0.0",
  "react-dom": "^18.0.0",
  "framer-motion": "^11.0.0",
  "three": "^0.160.0",
  "@react-three/fiber": "^8.0.0",
  "@react-three/drei": "^9.0.0"
}

TypeScript

DeckFX is written in TypeScript and exports all types:

import type { 
  DieType,            // 'd4' | 'd6' | 'd8' | 'd10' | 'd12' | 'd20'
  DieState,           // { id, type, value, target, isRolling }
  DieConfig,          // { color, duration, size, spinForce }
  ValidationResult,   // { valid: boolean; error?: string }
  DragStartValidator, // (cardId: string, cardData?: any) => Promise<ValidationResult>
  DragEndValidator,   // (cardId: string, fromZone: string, toZone: string, cardData?: any) => Promise<ValidationResult>
  ReorderValidator,   // (cardId: string, fromIndex: number, toIndex: number) => Promise<ValidationResult>
  DrawValidator,      // () => Promise<ValidationResult>
  ValidationConfig,   // Configuration for ValidationProvider
  ZoneLayout,         // Layout modes for CardZone
  CardRef,            // Ref type for Card component
  FlipDirection,      // 'horizontal' | 'vertical'
  Counter,            // Counter type for card counters
  CounterPosition,    // Counter position type
  SelectionMode,      // 'single' | 'multi' | 'none'
  SelectionContextValue // Selection context value type
} from 'deckfx';

Running the Demos Locally

# Install dependencies
npm install

# Run development server
npm run dev

# Build for production (GitHub Pages)
npm run build:demo

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Run linting
npm run lint

Contributing

Contributions to the demos are welcome! If you'd like to add a new demo or improve existing ones:

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/new-demo)
  3. Commit your changes (git commit -m 'Add new poker demo')
  4. Push to the branch (git push origin feature/new-demo)
  5. Open a Pull Request

For contributions to the DeckFX library itself, please visit the npm package repository.

License

MIT License - see LICENSE file for details.

Links