Scratch Card
Scratch a metallic foil off with your finger — past the threshold it dissolves in sparks.
- Zero dependencies
- React + Tailwind
- Accessible
- Reduced-motion aware
Preview
Animated · Full motion: idle animation, physics and effects. animated (default)
A brushed-metal foil with glitter and an embossed hint is painted on a canvas. Scratching erases it with a smooth stroke; once enough is cleared, the rest fades away and sparks burst out.
Installation
$ npx shadcn@latest add https://guetix.com/r/scratch-card.json
Uses the shadcn CLI. It creates components/guetix/scratch-card.tsx and installs nothing else.
Usage
example.tsx
"use client";
import ScratchCard from "@/components/guetix/scratch-card";
export default function ScratchCardDemo({ variant = "animated" }: { variant?: string }) {
return (
<ScratchCard animated={variant !== "regular"} hint="Scratch to reveal">
<div
className="grid h-full place-items-center text-center"
style={{ background: "radial-gradient(circle at 30% 20%, #f472b6, transparent 50%), radial-gradient(circle at 80% 80%, #22d3ee, transparent 50%), #0b0b10" }}
>
<div>
<p className="font-mono text-[10px] uppercase tracking-[0.35em] text-white/70">You won</p>
<p className="text-5xl font-black text-white">-30%</p>
<p className="mt-1 font-mono text-xs text-white/70">code: SCRATCHED</p>
</div>
</div>
</ScratchCard>
);
}
Promo codesGiveawaysSpoiler revealsGamified onboarding
Under the hood
The details that make it feel right, and that a one-line prompt usually misses:
- 01Strokes use `destination-out` lines between pointer samples, so fast swipes leave no gaps.
- 02Coverage is sampled every 16th pixel and only every 8th move to keep it cheap.
- 03The canvas is DPR-aware and repaints on resize via ResizeObserver.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Revealed content. |
| hint | string | "Scratch here" | Embossed text. |
| threshold | number | 0.5 | Share to clear before auto-reveal. |
| brush | number | 22 | Brush radius (px). |
| onReveal | () => void | — | Fired on reveal. |
| animated | boolean | true | false = the calm “Regular” version (no idle or ambient motion). |
Accessibility
- A visible "Reveal" button for keyboard and screen readers.
- The hidden content is `aria-hidden` until revealed.