Components
Scratchable card that reveals hidden content underneath. Drag to scratch off a metallic surface with realistic shimmer, crosshatch patterns, and tiny debris particles that fly off as you scratch. Tracks reveal percentage and auto-completes when the threshold is reached — the remaining surface fades away. Canvas eraser layered over DOM content so anything can go underneath: discounts, prizes, promo codes, images. Three surface patterns: crosshatch, dots, and plain. Configurable brush size, surface colors, completion threshold, and border radius. Callbacks for reveal progress and completion. Zero external dependencies.
Loading preview...
import { Component as ScratchCard } from "@/components/ui/scratch-card";
import { useState } from "react";
import { Gift, Sparkles, PartyPopper, Tag } from "lucide-react";
function DiscountReveal() {
const [revealed, setRevealed] = useState(false);
return (
<div className="w-full max-w-sm">
<ScratchCard
className="w-full h-[200px]"
surfaceFrom="#b8b8b8"
surfaceTo="#d4d4d4"
brushSize={30}
completeThreshold={55}
onComplete={() => setRevealed(true)}
pattern="crosshatch"
radius={16}
>
<div className="w-full h-full bg-gradient-to-br from-violet-600 to-indigo-700 flex flex-col items-center justify-center gap-3 rounded-2xl p-6">
<PartyPopper className="size-8 text-amber-300" />
<p className="text-4xl font-black text-white tracking-tight">40% OFF</p>
<p className="text-sm text-violet-200">Use code SCRATCH40</p>
{revealed && (
<div className="absolute inset-0 flex items-center justify-center bg-violet-600/20 rounded-2xl backdrop-blur-[1px]">
<Sparkles className="size-5 text-amber-300 animate-pulse" />
</div>
)}
</div>
</ScratchCard>
<p className="text-xs text-neutral-500 mt-2 text-center">
Scratch to reveal your discount
</p>
</div>
);
}
function PrizeReveal() {
return (
<div className="w-full max-w-sm">
<ScratchCard
className="w-full h-[200px]"
surfaceFrom="#c9a84c"
surfaceTo="#e8d48b"
brushSize={26}
completeThreshold={50}
pattern="dots"
radius={16}
>
<div className="w-full h-full bg-gradient-to-br from-emerald-600 to-teal-700 flex flex-col items-center justify-center gap-3 rounded-2xl p-6">
<Gift className="size-8 text-emerald-200" />
<p className="text-2xl font-bold text-white">Free Shipping</p>
<p className="text-sm text-emerald-200">On your next order</p>
</div>
</ScratchCard>
<p className="text-xs text-neutral-500 mt-2 text-center">
Gold surface — scratch to win
</p>
</div>
);
}
function SecretReveal() {
return (
<div className="w-full max-w-sm">
<ScratchCard
className="w-full h-[200px]"
surfaceFrom="#2a2a2a"
surfaceTo="#404040"
brushSize={24}
completeThreshold={45}
pattern="none"
radius={16}
>
<div className="w-full h-full bg-gradient-to-br from-rose-600 to-pink-700 flex flex-col items-center justify-center gap-3 rounded-2xl p-6">
<Tag className="size-8 text-rose-200" />
<p className="text-2xl font-bold text-white">LAUNCH2025</p>
<p className="text-sm text-rose-200">Early access code revealed</p>
</div>
</ScratchCard>
<p className="text-xs text-neutral-500 mt-2 text-center">
Dark surface — no pattern variant
</p>
</div>
);
}
export default function Demo() {
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-[#030712] px-6 py-16">
<div className="w-full max-w-lg mx-auto">
<div className="text-center mb-12">
<h2 className="text-2xl font-bold tracking-tight text-white mb-2">
Scratch Card
</h2>
<p className="text-sm text-neutral-400 max-w-md mx-auto">
Drag to scratch off the surface and reveal hidden content. Metallic
sheen, scratch particles, auto-complete detection. Perfect for
promos, gamification, and reveals.
</p>
</div>
<div className="flex flex-col items-center gap-8">
<DiscountReveal />
<PrizeReveal />
<SecretReveal />
</div>
</div>
</div>
);
}