Installation
There is no package. Distribution is this repository: take the folders you need, and they are yours to edit.
That is a deliberate choice with a real cost: you do not get updates by bumping a version. What you get instead is a component you can change without fighting a maintainer's abstraction. For UI this brand-specific, that is the right trade.
Requirements
React 18 or later. That is the only runtime dependency. Components ship plain CSS files, so no build plugin, CSS-in-JS runtime or Tailwind installation is required.
Copy the files
git clone https://github.com/mrcl-st/loyalty-patternsTake two things:
registry/tokens.css— the token defaults. Once, for the project.registry/<component>/— the folder for each component you want.
A component folder plus tokens.css is a working install, and that is the
whole repository: components, their engines, their styles and their
documentation. There is nothing to install and nothing to run — no
dependencies to add, no dev server, no build step of its own. The
repository is a source you copy from, not an app you clone and start.
Which is why the docs and the playground are here on this site rather than in the repository. What you see running on these pages is a separate application; what you take away is the folder.
Wire it up
Import the tokens once, near your global styles:
import "./tokens.css";Then import the component's own stylesheet and the component:
import "@/components/scratch-card/styles.css";
import { ScratchCard } from "@/components/scratch-card/scratch-card";Both stylesheets are plain CSS. If your bundler handles a CSS import, and Next.js, Vite, CRA and Remix all do, you are finished.
The one rule that matters
Components never decide outcomes. Pass a result your server already decided:
<ScratchCard
getOutcome={() => fetch("/api/draw", { method: "POST" }).then((r) => r.json())}
renderOutcome={(o) => (o.kind === "win" ? <Prize o={o} /> : <NoWin />)}
labels={{
hint: "Scratch to reveal",
revealButton: "Reveal without scratching",
announce: (o) => (o.kind === "win" ? "You won 500 points." : "No win this time."),
resolving: "Checking your result",
error: "We couldn't check your result.",
retry: "Try again",
}}
/>getOutcome is called once, when the member starts interacting, so the
network round-trip hides inside the gesture. If the result is already
known — a restored session, a prior play — pass outcome instead.
All copy arrives through labels, including the screen-reader
announcement. There are no hardcoded strings to find and translate
later.
Updating
Copy the folder again and re-apply your edits, or diff against the repository. Because you own the files, an update is a merge you control rather than a version bump that changes behaviour you did not ask about.
Component folders are self-contained on purpose, so a diff never spans the project.