Skip to main content

ScratchCard

ScratchCard is the low-level reveal primitive from @luxy-creative/scratch-card. Use it when the host app owns the layout under the cover or needs a custom reward surface.

Basic card

import { ScratchCard } from "@luxy-creative/scratch-card";

<ScratchCard
outcome={outcome}
cover="foil"
renderReveal={(outcome, state) => <RewardCard outcome={outcome} progress={state.progress} />}
/>;

Backend-resolved card

<ScratchCard
resolveOutcome={(context) => api.reserveReward(context.interactionId)}
cover="premium"
effects={{ particles: { maxParticles: 72 }, shader: "auto" }}
renderPendingReveal={() => <RewardSkeleton />}
renderReveal={(outcome, state) => <RewardCard outcome={outcome} progress={state.progress} />}
onRevealComplete={(outcome) => analytics.track("scratch_revealed", { outcomeId: outcome.id })}
/>;

Core props

PropPurpose
renderReveal(outcome, state)Renders reward content under the cover.
renderPendingReveal()Renders while an async resolver is pending.
coverAccepts matte, foil, premium, a React node, or a render callback.
scratchZonesLimits cover rendering and progress estimation to normalized play areas.
revealThresholdDefaults to 0.62; triggers onRevealThreshold once.
autoRevealEnables threshold/time-based cover finish behavior.
effectsControls shimmer, particles, sparkle, foil, shader fallback, and quality.
gestureRelationsForwards Gesture Handler relation methods for nested scroll/carousel conflict handling.

Accessibility

<ScratchCard
outcome={outcome}
cover="foil"
accessibility={{
label: "Scratch card. Double tap to reveal your reward.",
hint: "Reveals the promotional coupon.",
screenReaderRevealMode: "press",
}}
renderReveal={renderReward}
/>;

screenReaderRevealMode: "press" keeps the card operable without a scratch gesture.

Gesture conflict controls

Scroll-first offer feed:

const feedPan = useMemo(() => Gesture.Native(), []);

<ScratchCard
outcome={outcome}
gestureRelations={{ requireExternalGestureToFail: feedPan }}
renderReveal={renderReward}
/>;

Carousel where horizontal swipes and scratching can coexist:

<ScratchCard
outcome={outcome}
gestureRelations={{ simultaneousWithExternalGesture: carouselGesture }}
renderReveal={renderReward}
/>;

Keep relation objects stable with useMemo or refs in host app code.

Callbacks

  • onOutcomeResolved(outcome)
  • onScratchStart()
  • onProgress(progress)
  • onRevealThreshold(outcome)
  • onRevealComplete(outcome)
  • onError(error)

Callbacks should not contain secrets, full customer records, raw coupon tokens, or sensitive eligibility data.