PlayingCard Component

Welcome to the documentation for PlayingCard, a fully interactive React component that displays realistic, animated playing cards with seamless flip transitions. Quickly build card UIs or prototype games and experiences—plus, explore the advanced PlayingCardWithToggle example for rapid prototyping.

At a Glance

A of Hearts
10 of Clubs

The PlayingCard component renders a crisp, visually rich playing card. It animates between card faces with a 3D flip effect whenever the displayed suite or rank changes. To show the back of the card, just provide null for both props.


Installation

Only clsx and motion are needed. If you don’t have them, install with your preferred package manager:

npm install clsx motion

Place the images in /public/cards/. Naming convention:

  • /cards/card<Suite><Rank>.png (e.g. /cards/cardHeartsA.png)
  • /cards/cardBack_blue4.png (for the card back)

Component Source

PlayingCard

'use client'
import { useEffect, useState } from 'react'
import { motion } from 'motion/react'
import clsx from 'clsx'
import Image from 'next/image'

export type Suite = 'Spades' | 'Hearts' | 'Diamonds' | 'Clubs'
export type Rank = '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | 'J' | 'Q' | 'K' | 'A'

export interface PlayingCardProps {
    suite: Suite | null
    rank: Rank | null
    className?: string
}

const PlayingCard = ({ suite, rank, className }: PlayingCardProps) => {
    const [isFlipped, setIsFlipped] = useState(false)
    const [currentSuite, setCurrentSuite] = useState(suite)
    const [currentRank, setCurrentRank] = useState(rank)

    useEffect(() => {
        if (suite === currentSuite && rank === currentRank) return

        setIsFlipped(true)
        const timer = setTimeout(() => {
            setCurrentSuite(suite)
            setCurrentRank(rank)
            setIsFlipped(false)
        }, 200)

        return () => clearTimeout(timer)
    }, [suite, rank, currentSuite, currentRank])

    const cardImageSrc =
        currentSuite && currentRank
            ? `/cards/card${currentSuite}${currentRank}.png`
            : '/cards/cardBack_blue4.png'
    const cardAlt =
        currentSuite && currentRank
            ? `${currentRank} of ${currentSuite}`
            : 'Card back'

    return (
        <div className={clsx('w-[100px] h-[140px] select-none', className)}>
            <motion.div
                className="w-full h-full relative"
                initial={false}
                animate={{
                    rotateY: isFlipped ? 90 : 0,
                    scale: isFlipped ? 1.05 : 1,
                    boxShadow: isFlipped
                        ? '0px 10px 25px rgba(0, 0, 0, 0.2)'
                        : '0px 5px 15px rgba(0, 0, 0, 0.1)',
                }}
                transition={{ type: 'spring', stiffness: 350, damping: 22, duration: 0.2 }}
                whileHover={{
                    y: -5,
                    boxShadow: '0px 15px 30px rgba(0, 0, 0, 0.3)',
                    transition: { duration: 0.2 },
                }}
                style={{
                    perspective: 600,
                    willChange: 'transform',
                }}
                aria-label={cardAlt}
            >
                <motion.div>
                    <Image
                        src={cardImageSrc}
                        alt={cardAlt}
                        draggable={false}
                        className="w-full h-full object-cover select-none pointer-events-none"
                        loading="eager"
                        decoding="async"
                        width={100}
                        height={140}
                    />
                </motion.div>
            </motion.div>
        </div>
    )
}

export default PlayingCard

Usage

To use, just import and render:

import PlayingCard from '@/components/Examples/PlayingCard'

<PlayingCard rank="K" suite="Diamonds" />
<PlayingCard rank={null} suite={null} /> {/* Renders card back */}

Props

NameTypeDefaultDescription
suite'Spades' | 'Hearts' | 'Diamonds' | 'Clubs' | nullWhich suite to display. Use null for card back
rank'2'–'10', 'J', 'Q', 'K', 'A' | nullThe rank value. Use null with suite for back of card
classNamestringAdditional CSS classes for custom styling of the outer wrapper

Live Example

2 of Spades
2 of Spades

Assets

  • Place card images in /public/cards/card<Suite><Rank>.png (e.g. /cards/cardHeartsA.png)
  • Card back image: /public/cards/cardBack_blue4.png
  • Feel free to use your own styles or image set if preferred!

Accessibility

  • The underlying image has an informative aria-label for screen readers
  • No text is leaked when showing the card back
  • Non-interactive by default—add your own controls or event logic as needed
  • Wrap in button or add keyboard logic for full accessibility, if desired

What's next?

Was this page helpful?