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

ink-playing-cards

v1.1.0

Published

A ink component to render playing cards and card games

Readme

Ink Playing Cards

A flexible framework for building terminal-based card games with React and Ink. Components, hooks, and game systems for managing card state and rendering in the terminal.

Requirements

  • Node.js >= 20
  • React >= 19
  • Ink >= 6

Installation

npm install ink-playing-cards

Quick Start

import React from 'react'
import { render } from 'ink'
import { Box, Text } from 'ink'
import { DeckProvider, useDeck, CardStack } from 'ink-playing-cards'

const Game = () => {
  const { deck, hands, shuffle, draw } = useDeck()

  React.useEffect(() => {
    shuffle()
    draw(5, 'player1')
  }, [])

  return (
    <Box flexDirection="column">
      <Text>Deck: {deck.length} cards</Text>
      <CardStack cards={deck} name="Deck" maxDisplay={1} />
      <Text>Your hand:</Text>
      <CardStack
        cards={hands['player1'] ?? []}
        name="Hand"
        isFaceUp
        maxDisplay={5}
      />
    </Box>
  )
}

render(
  <DeckProvider>
    <Game />
  </DeckProvider>
)

Features

  • Standard and custom card components with multiple display variants (simple, ascii, minimal, mini, micro, unicode)
  • Zone system (Deck, Hand, Discard Pile, Play Area) for managing game areas
  • Event system for responding to draws, plays, discards, shuffles, and other game actions
  • Effect system supporting conditional, triggered, continuous, delayed, and targeted effects
  • Hooks (useDeck, useHand) for accessing game state and dispatching actions
  • GameProvider context for turn management and game phases
  • Theming support for ASCII card art (original, geometric, animal, robot, pixel, medieval)
  • Custom card component with structured layout regions and freeform mode
  • Utility functions: createStandardDeck, createPairedDeck

Architecture

DeckProvider (context + useReducer)
├── zones: { deck, hands, discardPile, playArea }  ← immutable TCard[] arrays
├── players: string[]
├── eventManager: EventManager                      ← game event dispatch
├── effectManager: EffectManager                    ← card effect evaluation
└── dispatch(action)                                ← state updates via actions

All state flows through the DeckProvider reducer. The useDeck hook wraps dispatch calls into convenient functions. Zones are plain arrays — the reducer returns new arrays on every action.

Components

Card

Standard playing card with multiple display variants:

<Card
  id="hearts-A-abc123"
  suit="hearts"
  value="A"
  variant="simple"   // 'simple' | 'ascii' | 'minimal'
  theme="original"   // 'original' | 'geometric' | 'animal' | 'robot' | 'pixel' | 'medieval'
  faceUp={true}
  selected={false}
  rounded={true}
/>

MiniCard

Compact card for space-efficient layouts:

<MiniCard
  id="spades-K-def456"
  suit="spades"
  value="K"
  variant="mini"     // 'mini' | 'micro'
  faceUp={true}
/>

UnicodeCard

Single-character Unicode card using standard playing card symbols (U+1F0A0–U+1F0FF):

<UnicodeCard
  suit="hearts"
  value="A"
  faceUp={true}
  bordered={false}
  rounded={true}
  size={1}
  dimmed={false}
/>

Includes all 52 standard cards, jokers, and card back symbol with automatic suit-based coloring.

CustomCard

Freeform card for non-standard card games (TCG, color-matching, party games):

<CustomCard
  id="flame-lance"
  size="large"        // 'micro' | 'mini' | 'small' | 'medium' | 'large'
  title="Flame Lance"
  cost="{R}"
  asciiArt={`  /\\_/\\`}
  typeLine="Instant"
  description="Deal 3 damage to any target."
  footerLeft="3/4"
  footerRight="R"
  symbols={[{ char: '★', position: 'top-left', color: 'yellow' }]}
  borderColor="red"
  textColor="white"
  faceUp={true}
/>

Supports structured layout (title, cost, art, typeLine, description, footer) or freeform mode via content prop. Custom card backs via the back prop.

CardStack

Displays cards in a horizontal or vertical stack. Supports both standard and custom cards:

<CardStack
  cards={handCards}
  name="Player Hand"
  variant="simple"
  stackDirection="horizontal"  // 'horizontal' | 'vertical'
  spacing={{ overlap: -2, margin: 1 }}
  maxDisplay={5}
  isFaceUp={true}
/>

CardGrid

Arranges cards in a grid layout:

<CardGrid
  rows={3}
  cols={3}
  cards={gameBoard}
  variant="simple"
  spacing={{ row: 1, col: 1 }}
  fillEmpty={true}
  isFaceUp={true}
/>

Deck

Displays the deck with an optional top card preview. Must be used inside a DeckProvider:

<Deck
  variant="simple"
  showTopCard={true}
  placeholderCard={{ suit: 'hearts', value: 'A' }}
/>

Hooks

useDeck

Primary hook for deck operations. Must be used inside a DeckProvider.

const {
  deck,           // TCard[] — current deck
  hands,          // Record<string, TCard[]> — player hands by ID
  discardPile,    // TCard[] — discard pile
  playArea,       // TCard[] — play area
  players,        // string[] — registered player IDs
  backArtwork,    // BackArtwork — card back art per variant
  eventManager,   // EventManager — subscribe to game events
  effectManager,  // EffectManager — apply card effects
  shuffle,        // () => void
  draw,           // (count, playerId) => void
  reset,          // (cards?) => void
  deal,           // (count, playerIds) => void
  cutDeck,        // (index) => void
  addPlayer,      // (playerId) => void
  removePlayer,   // (playerId) => void
  getPlayerHand,  // (playerId) => TCard[]
  addCustomCard,  // (card: CustomCardProps) => void
  removeCustomCard, // (cardId) => void
  setBackArtwork, // (artwork: Partial<BackArtwork>) => void
} = useDeck()

useHand

Convenience hook for a specific player's hand:

const { hand, drawCard, playCard, discard } = useHand('player1')

drawCard(2)              // draw 2 cards
playCard('card-id')      // move card to play area
discard('card-id')       // move card to discard pile

Contexts

DeckProvider

Wraps your game and provides deck state via React context + useReducer:

<DeckProvider initialCards={customCards}>
  <Game />
</DeckProvider>

Accepts optional initialCards (defaults to standard 52-card deck) and customReducer for extending the reducer.

GameProvider

Manages turn order, current player, and game phases:

<GameProvider initialPlayers={['alice', 'bob']}>
  <Game />
</GameProvider>

Dispatches SET_CURRENT_PLAYER, NEXT_TURN, and SET_PHASE actions.

Core Systems

Zones

Pure utility functions for immutable zone operations:

import { Zones } from 'ink-playing-cards'

const shuffled = Zones.shuffleCards(deck)
const [drawn, remaining] = Zones.drawCards(deck, 5)
const withCard = Zones.addCard(hand, card)
const without = Zones.removeCard(hand, 'card-id')
const found = Zones.findCard(deck, 'card-id')
const cut = Zones.cutDeck(deck, 26)

Also exports legacy class-based zones (Deck, Hand, DiscardPile, PlayArea) for standalone use.

Events

Subscribe to game events dispatched by the reducer:

const { eventManager } = useDeck()

const listener = {
  handleEvent(event) {
    console.log(event.type, event.playerId, event.cards)
  }
}

eventManager.addEventListener('CARDS_DRAWN', listener)
eventManager.removeEventListener('CARDS_DRAWN', listener)
eventManager.removeAllListeners()

Event types: CARDS_DRAWN, CARDS_DEALT, CARD_PLAYED, CARD_DISCARDED, DECK_SHUFFLED, DECK_RESET, DECK_CUT, plus custom strings.

Effects

Attach effects to cards for complex game mechanics:

import { Effects } from 'ink-playing-cards'

const fireball = new Effects.DamageEffect(3)
const drawTwo = new Effects.DrawCardEffect(2)
const timeBomb = new Effects.DelayedEffect(3, new Effects.DamageEffect(5))
const conditional = new Effects.ConditionalEffect(
  (gs) => gs.turn > 3,
  new Effects.DrawCardEffect(1)
)
const triggered = new Effects.TriggeredEffect('CARD_PLAYED', fireball)

Effects.attachEffectToCard(card, fireball)
effectManager.applyCardEffects(card, gameState, eventData)

Effect types: ConditionalEffect, TriggeredEffect, ContinuousEffect, DelayedEffect, TargetedEffect, DrawCardEffect, DamageEffect.

Utilities

import { createStandardDeck, createPairedDeck, generateCardId } from 'ink-playing-cards'

const deck = createStandardDeck()       // 52 cards with unique IDs
const pairs = createPairedDeck()        // paired deck for Memory-style games
const id = generateCardId('hearts', 'A') // "hearts-A-abc123"

Type Guards

import { isStandardCard, isCustomCard } from 'ink-playing-cards'

if (isStandardCard(card)) {
  // card.suit, card.value available
}
if (isCustomCard(card)) {
  // card.title, card.description, etc. available
}

Development

yarn install     # Install dependencies
yarn build       # Compile TypeScript to dist/
yarn test        # Build + run tests
yarn lint        # Check formatting + lint
yarn lint:fix    # Auto-fix formatting + lint
yarn dev         # Run interactive component showcase

Interactive Showcase

The library includes a storybook-style CLI showcase for exploring components interactively:

yarn dev

Navigate through components, configure props, and see live previews in the terminal.

Examples

The examples/ directory contains markdown guides for building various card games:

See the full list in examples/.

License

MIT — see LICENSE for details.